├── tests ├── cases │ ├── hooks │ │ ├── index.js │ │ ├── style.css │ │ └── expected.css │ ├── simple │ │ ├── index.js │ │ ├── expected.css │ │ └── style.css │ ├── filename │ │ ├── index.js │ │ ├── expected.css │ │ └── style.css │ ├── minimize │ │ ├── index.js │ │ ├── expected.css │ │ └── style.css │ ├── options │ │ ├── index.js │ │ ├── style.css │ │ └── expected.css │ ├── plugins │ │ ├── index.js │ │ ├── expected.css │ │ └── style.css │ └── sourcemap │ │ ├── index.js │ │ ├── style.css │ │ └── expected.css ├── bundle.ts └── plugin.test.ts ├── .travis.yml ├── src ├── index.ts ├── types │ └── rtlcss.d.ts └── plugin.ts ├── .gitattributes ├── .prettierrc ├── tsconfig.test.json ├── .babelrc ├── .vscode └── settings.json ├── jest.config.js ├── tsconfig.json ├── LICENSE ├── .gitignore ├── package.json └── README.md /tests/cases/hooks/index.js: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | -------------------------------------------------------------------------------- /tests/cases/simple/index.js: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | module.exports = require("./plugin").default; -------------------------------------------------------------------------------- /tests/cases/filename/index.js: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | -------------------------------------------------------------------------------- /tests/cases/minimize/index.js: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | -------------------------------------------------------------------------------- /tests/cases/options/index.js: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | -------------------------------------------------------------------------------- /tests/cases/plugins/index.js: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | -------------------------------------------------------------------------------- /tests/cases/sourcemap/index.js: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 100, 4 | "parser": "typescript" 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/cases/minimize/expected.css: -------------------------------------------------------------------------------- 1 | .example{direction:rtl;margin:0;padding:1em 1em .5em 2em;background-color:#353639;font-family:Droid Arabic Kufi,Droid Sans,sans-serif;font-size:14px} -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": "6.11.5" 8 | } 9 | } 10 | ] 11 | ] 12 | } -------------------------------------------------------------------------------- /tests/cases/filename/expected.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: rtl; 3 | margin: 0; 4 | padding: 1em 1em .5em 2em; 5 | background-color: #353639; 6 | font-family: "Droid Arabic Kufi","Droid Sans", sans-serif; 7 | font-size: 14px; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cases/plugins/expected.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: rtl; 3 | margin: 0; 4 | padding: 1em 1em .5em 2em; 5 | background-color: #353639; 6 | font-family: "Droid Arabic Kufi","Droid Sans", sans-serif; 7 | font-size: 18px; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cases/simple/expected.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: rtl; 3 | margin: 0; 4 | padding: 1em 1em .5em 2em; 5 | background-color: #353639; 6 | font-family: "Droid Arabic Kufi","Droid Sans", sans-serif; 7 | font-size: 14px; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cases/filename/style.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: ltr; 3 | margin: 0; 4 | padding: 1em 2em .5em 1em; 5 | background-color: #353639; 6 | font-family: "Droid Sans", sans-serif/*rtl:prepend:"Droid Arabic Kufi",*/; 7 | font-size: 16px/*rtl:14px*/; 8 | } -------------------------------------------------------------------------------- /tests/cases/hooks/style.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: ltr; 3 | margin: 0; 4 | padding: 1em 2em .5em 1em; 5 | background-color: #353639; 6 | font-family: "Droid Sans", sans-serif/*rtl:prepend:"Droid Arabic Kufi",*/; 7 | font-size: 16px/*rtl:14px*/; 8 | } -------------------------------------------------------------------------------- /tests/cases/minimize/style.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: ltr; 3 | margin: 0; 4 | padding: 1em 2em .5em 1em; 5 | background-color: #353639; 6 | font-family: "Droid Sans", sans-serif/*rtl:prepend:"Droid Arabic Kufi",*/; 7 | font-size: 16px/*rtl:14px*/; 8 | } -------------------------------------------------------------------------------- /tests/cases/options/style.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: ltr; 3 | margin: 0; 4 | padding: 1em 2em .5em 1em; 5 | background-color: #353639; 6 | font-family: "Droid Sans", sans-serif/*rtl:prepend:"Droid Arabic Kufi",*/; 7 | font-size: 16px/*rtl:14px*/; 8 | } -------------------------------------------------------------------------------- /tests/cases/simple/style.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: ltr; 3 | margin: 0; 4 | padding: 1em 2em .5em 1em; 5 | background-color: #353639; 6 | font-family: "Droid Sans", sans-serif/*rtl:prepend:"Droid Arabic Kufi",*/; 7 | font-size: 16px/*rtl:14px*/; 8 | } -------------------------------------------------------------------------------- /tests/cases/sourcemap/style.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: ltr; 3 | margin: 0; 4 | padding: 1em 2em .5em 1em; 5 | background-color: #353639; 6 | font-family: "Droid Sans", sans-serif/*rtl:prepend:"Droid Arabic Kufi",*/; 7 | font-size: 16px/*rtl:14px*/; 8 | } -------------------------------------------------------------------------------- /tests/cases/plugins/style.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: ltr; 3 | margin: 0; 4 | padding: 1em 2em .5em 1em; 5 | background-color: #353639; 6 | font-family: "Droid Sans", sans-serif/*rtl:prepend:"Droid Arabic Kufi",*/; 7 | font-size: 16px/*rtl:bigger-font-size*/; 8 | } -------------------------------------------------------------------------------- /tests/cases/hooks/expected.css: -------------------------------------------------------------------------------- 1 | /* Generated by RTLCSS */ 2 | .example { 3 | direction: rtl; 4 | margin: 0; 5 | padding: 1em 1em .5em 2em; 6 | background-color: #353639; 7 | font-family: "Droid Arabic Kufi","Droid Sans", sans-serif; 8 | font-size: 14px; 9 | } 10 | -------------------------------------------------------------------------------- /tests/cases/options/expected.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: rtl; 3 | margin: 0; 4 | padding: 1em 1em .5em 2em; 5 | background-color: #353639; 6 | font-family: "Droid Arabic Kufi","Droid Sans", sans-serif/*rtl:prepend:"Droid Arabic Kufi",*/; 7 | font-size: 14px/*rtl:14px*/; 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "prettier.jsxBracketSameLine": true, 4 | "editor.minimap.enabled": false, 5 | "typescript.tsdk": "node_modules\\typescript\\lib", 6 | "files.exclude": { 7 | "node_modules": true, 8 | "tsc": true, 9 | "dist": true 10 | }, 11 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | "^.+\\.ts$": "ts-jest" 4 | }, 5 | testRegex: ".test.ts", 6 | moduleFileExtensions: ["ts", "js", "json", "node"], 7 | globals: { 8 | "ts-jest": { 9 | tsConfigFile: "tsconfig.test.json" 10 | } 11 | }, 12 | coveragePathIgnorePatterns: ["node_modules", "tests/bundle.ts"] 13 | }; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "es2015", 5 | "outDir": "./tsc", 6 | "moduleResolution": "node", 7 | "allowSyntheticDefaultImports": true, 8 | "alwaysStrict": true, 9 | "strictFunctionTypes": true, 10 | "strictPropertyInitialization": true, 11 | "strictNullChecks": true, 12 | "noImplicitAny": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | }, 16 | "include": [ 17 | "./src" 18 | ] 19 | } -------------------------------------------------------------------------------- /tests/cases/sourcemap/expected.css: -------------------------------------------------------------------------------- 1 | .example { 2 | direction: rtl; 3 | margin: 0; 4 | padding: 1em 1em .5em 2em; 5 | background-color: #353639; 6 | font-family: "Droid Arabic Kufi","Droid Sans", sans-serif; 7 | font-size: 14px; 8 | } 9 | 10 | /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxpbnB1dCBjc3MgMz4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7R0FDRyxlQUFlO0dBQ2YsVUFBVTtHQUNWLDBCQUEwQjtHQUMxQiwwQkFBMEI7R0FDMUIsMERBQTBFO0dBQzFFLGdCQUE0QjtDQUM5QiIsImZpbGUiOiJ0by5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIuZXhhbXBsZSB7XG4gICBkaXJlY3Rpb246IGx0cjtcbiAgIG1hcmdpbjogMDtcbiAgIHBhZGRpbmc6IDFlbSAyZW0gLjVlbSAxZW07XG4gICBiYWNrZ3JvdW5kLWNvbG9yOiAjMzUzNjM5O1xuICAgZm9udC1mYW1pbHk6IFwiRHJvaWQgU2Fuc1wiLCBzYW5zLXNlcmlmLypydGw6cHJlcGVuZDpcIkRyb2lkIEFyYWJpYyBLdWZpXCIsKi87XG4gICBmb250LXNpemU6IDE2cHgvKnJ0bDoxNHB4Ki87XG59XG4iXX0= */ -------------------------------------------------------------------------------- /src/types/rtlcss.d.ts: -------------------------------------------------------------------------------- 1 | declare module "rtlcss" { 2 | type Options = any; 3 | type Plugins = any; 4 | type Hooks = any; 5 | type Map = any; 6 | 7 | type Configure = (config: Config | undefined) => { process: PostCssProcess }; 8 | type Process = (css: string, options: Options, plugins: Plugins, hooks: Hooks) => string; 9 | type PostCssProcess = (css: string, options: PostcssOptions | undefined) => Result; 10 | 11 | const rtlcss: { 12 | configure: Configure; 13 | process: Process; 14 | }; 15 | 16 | export type Config = { 17 | options: Options; 18 | plugins: Plugins[]; 19 | hooks: Hooks[]; 20 | }; 21 | 22 | export type PostcssOptions = { 23 | map: boolean; 24 | }; 25 | 26 | export type Result = { 27 | css: string; 28 | map: Map; 29 | }; 30 | 31 | export default rtlcss; 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nicolas Hémonic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # Build 61 | /tsc 62 | /dist 63 | 64 | # Coverage 65 | /coverage 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtl-css-transform-webpack-plugin", 3 | "version": "1.0.6", 4 | "description": "Webpack plugin for transforming CSS from LTR to RTL.", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "jest --coverage", 8 | "prebuild": "npm run clean-dist", 9 | "build": "tsc && babel tsc -d dist", 10 | "postbuild": "npm run clean-tsc", 11 | "prepublish": "npm run build", 12 | "clean-dist": "rimraf dist", 13 | "clean-tsc": "rimraf tsc" 14 | }, 15 | "keywords": [ 16 | "rtl", 17 | "rtlcss", 18 | "webpack", 19 | "plugin" 20 | ], 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/nicolashemonic/rtl-css-transform-webpack-plugin.git" 24 | }, 25 | "bugs": "https://github.com/nicolashemonic/rtl-css-transform-webpack-plugin/issues?state=open", 26 | "author": "Nicolas Hémonic", 27 | "license": "MIT", 28 | "files": [ 29 | "dist" 30 | ], 31 | "dependencies": { 32 | "rtlcss": "^2.2.1", 33 | "webpack-sources": "^1.1.0" 34 | }, 35 | "devDependencies": { 36 | "@types/jest": "^22.2.3", 37 | "@types/memory-fs": "^0.3.0", 38 | "@types/mini-css-extract-plugin": "^0.2.0", 39 | "@types/webpack": "^4.1.3", 40 | "@types/webpack-sources": "^0.1.4", 41 | "babel-cli": "^6.26.0", 42 | "babel-preset-env": "^1.6.1", 43 | "css-loader": "^0.28.11", 44 | "jest": "^22.4.3", 45 | "memory-fs": "^0.4.1", 46 | "mini-css-extract-plugin": "^0.4.0", 47 | "optimize-css-assets-webpack-plugin": "^4.0.0", 48 | "rimraf": "^2.6.2", 49 | "ts-jest": "^22.4.4", 50 | "typescript": "^2.8.3", 51 | "uglifyjs-webpack-plugin": "^1.2.5", 52 | "webpack": "^4.6.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/bundle.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import webpack from "webpack"; 3 | import MemoryFS from "memory-fs"; 4 | import RtlCssPlugin, { IOptions } from "../src/plugin"; 5 | import MiniCssExtractPlugin from "mini-css-extract-plugin"; 6 | import OptimizeCSSAssetsPlugin from "optimize-css-assets-webpack-plugin"; 7 | import UglifyJsPlugin from "uglifyjs-webpack-plugin"; 8 | 9 | export const resolver = (...args) => path.resolve(__dirname, ...args); 10 | 11 | export const buildBundle = (caseName: string, options?: IOptions, minimize = false) => 12 | new Promise((resolve, reject) => { 13 | const compiler = webpack({ 14 | entry: resolver("cases", caseName), 15 | output: { 16 | path: resolver(), 17 | filename: "[name].js" 18 | }, 19 | resolveLoader: { 20 | modules: [resolver("..", "node_modules")] 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.css$/, 26 | use: [MiniCssExtractPlugin.loader, "css-loader"] 27 | } 28 | ] 29 | }, 30 | plugins: [new MiniCssExtractPlugin(), new RtlCssPlugin(options)], 31 | optimization: { 32 | minimizer: minimize ? [new UglifyJsPlugin(), new OptimizeCSSAssetsPlugin()] : [] 33 | } 34 | } as webpack.Configuration); 35 | 36 | compiler.outputFileSystem = new MemoryFS(); 37 | 38 | compiler.run((err, stats) => { 39 | if (err) { 40 | return reject(err); 41 | } else if (stats.hasErrors()) { 42 | return reject(new Error(stats.toString())); 43 | } else { 44 | resolve(compiler.outputFileSystem); 45 | } 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import rtlcss, { Config } from "rtlcss"; 3 | import { Source, RawSource } from "webpack-sources"; 4 | import webpack from "webpack"; 5 | 6 | export interface IOptions { 7 | filename?: string; 8 | sourcemap?: boolean | undefined; 9 | config?: Config | undefined; 10 | } 11 | 12 | const pluginName = "rtl-css-transform-webpack-plugin"; 13 | const isCss = (filename: string) => path.extname(filename) === ".css"; 14 | 15 | export default class RtlCssPlugin implements webpack.Plugin { 16 | private options: IOptions = { 17 | filename: "[name].rtl.css", 18 | sourcemap: undefined, 19 | config: undefined 20 | }; 21 | 22 | constructor(options?: IOptions) { 23 | if (typeof options === "object") { 24 | if (typeof options.filename === "string") { 25 | this.options.filename = options.filename; 26 | } 27 | if (typeof options.sourcemap === "boolean") { 28 | this.options.sourcemap = options.sourcemap; 29 | } 30 | if (typeof options.config === "object") { 31 | this.options.config = options.config; 32 | } 33 | } 34 | } 35 | 36 | public apply(compiler: webpack.Compiler) { 37 | const { filename, sourcemap, config } = this.options; 38 | const { devtool } = compiler.options; 39 | const postcssOptions = { 40 | map: (sourcemap === undefined && !!devtool) || !!sourcemap 41 | }; 42 | 43 | compiler.hooks.compilation.tap(pluginName, compilation => { 44 | compilation.hooks.optimizeChunkAssets.tapAsync(pluginName, (chunks, callback) => { 45 | chunks.forEach(chunk => { 46 | chunk.files.filter(isCss).forEach((chunkFilename: string) => { 47 | const asset: Source = compilation.assets[chunkFilename]; 48 | const result = rtlcss 49 | .configure(config) 50 | .process(asset.source(), postcssOptions); 51 | const rawSource = new RawSource(result.css); 52 | const assetFilename = compilation.getPath(filename as string, { 53 | chunk 54 | }); 55 | compilation.assets[assetFilename] = rawSource; 56 | }); 57 | }); 58 | callback(); 59 | }); 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/plugin.test.ts: -------------------------------------------------------------------------------- 1 | import { buildBundle, resolver } from "./bundle"; 2 | import fs from "fs"; 3 | import path from "path"; 4 | 5 | describe("rtl-css-transform-webpack-plugin", () => { 6 | it("Should transform LTR to RTL", async () => { 7 | expect.assertions(1); 8 | const bundle = await buildBundle("simple"); 9 | expect(bundle.readFileSync(resolver("main.rtl.css"))).toEqual( 10 | fs.readFileSync(resolver("cases", "simple", "expected.css")) 11 | ); 12 | }); 13 | 14 | it("Should change file name", async () => { 15 | expect.assertions(1); 16 | const bundle = await buildBundle("filename", { filename: "rtl/[id].css" }); 17 | expect(bundle.readFileSync(resolver("rtl", "0.css"))).toEqual( 18 | fs.readFileSync(resolver("cases", "filename", "expected.css")) 19 | ); 20 | }); 21 | 22 | it("Should generate source mapping", async () => { 23 | expect.assertions(1); 24 | const bundle = await buildBundle("sourcemap", { sourcemap: true }); 25 | expect(bundle.readFileSync(resolver("main.rtl.css"))).toEqual( 26 | fs.readFileSync(resolver("cases", "sourcemap", "expected.css")) 27 | ); 28 | }); 29 | 30 | it("Should minimize bundle", async () => { 31 | expect.assertions(1); 32 | const bundle = await buildBundle("minimize", undefined, true); 33 | expect(bundle.readFileSync(resolver("main.rtl.css"))).toEqual( 34 | fs.readFileSync(resolver("cases", "minimize", "expected.css")) 35 | ); 36 | }); 37 | 38 | it("Should take into account RTLCSS options", async () => { 39 | expect.assertions(1); 40 | const bundle = await buildBundle("options", { 41 | config: { 42 | options: { 43 | clean: false 44 | } 45 | } 46 | }); 47 | expect(bundle.readFileSync(resolver("main.rtl.css"))).toEqual( 48 | fs.readFileSync(resolver("cases", "options", "expected.css")) 49 | ); 50 | }); 51 | 52 | it("Should take into account RTLCSS plugins", async () => { 53 | expect.assertions(1); 54 | const bundle = await buildBundle("plugins", { 55 | config: { 56 | plugins: [ 57 | { 58 | name: "test-plugin", 59 | priority: 1, 60 | directives: { 61 | control: {}, 62 | value: [ 63 | { 64 | name: "bigger-font-size", 65 | action: function(decl, expr, context) { 66 | decl.raws.value.raw.replace(expr, function(match, value) { 67 | decl.value = decl.raws.value.raw = "18px"; 68 | }); 69 | return true; 70 | } 71 | } 72 | ] 73 | }, 74 | processors: [] 75 | } 76 | ] 77 | } 78 | }); 79 | expect(bundle.readFileSync(resolver("main.rtl.css"))).toEqual( 80 | fs.readFileSync(resolver("cases", "plugins", "expected.css")) 81 | ); 82 | }); 83 | 84 | it("Should take into account RTLCSS hooks", async () => { 85 | expect.assertions(1); 86 | const bundle = await buildBundle("hooks", { 87 | config: { 88 | hooks: { 89 | post: function(root, postcss) { 90 | root.insertBefore( 91 | root.nodes[0], 92 | postcss.comment({ text: "Generated by RTLCSS" }) 93 | ); 94 | } 95 | } 96 | } 97 | }); 98 | expect(bundle.readFileSync(resolver("main.rtl.css"))).toEqual( 99 | fs.readFileSync(resolver("cases", "hooks", "expected.css")) 100 | ); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RTL CSS Transform Webpack Plugin 💎 2 | 3 | [![npm version](https://badge.fury.io/js/rtl-css-transform-webpack-plugin.svg)](https://badge.fury.io/js/rtl-css-transform-webpack-plugin) 4 | [![Dependency Status](https://david-dm.org/nicolashemonic/rtl-css-transform-webpack-plugin.svg)](https://david-dm.org/nicolashemonic/rtl-css-transform-webpack-plugin) 5 | [![Build Status](https://travis-ci.org/nicolashemonic/rtl-css-transform-webpack-plugin.svg?branch=master)](https://travis-ci.org/nicolashemonic/rtl-css-transform-webpack-plugin) 6 | 7 | Webpack plugin that implements [RTLCSS](https://github.com/MohammadYounes/rtlcss) framework for transforming Left-To-Right (LTR) Cascading Style Sheets (CSS) to Right-To-Left (RTL). 8 | 9 | Before you should use a CSS extraction plugin. For Webpack v4 we use [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) to extract CSS from JS. 10 | 11 | ```rtl-css-transform-webpack-plugin``` creates RTL CSS file per LTR CSS file: 12 | 13 | style.css 14 | 15 | ```css 16 | .example { 17 | direction: ltr; 18 | margin: 0; 19 | padding: 1em 2em .5em 1em; 20 | background-color: #353639; 21 | font-family: "Droid Sans", sans-serif/*rtl:prepend:"Droid Arabic Kufi",*/; 22 | font-size: 16px/*rtl:14px*/; 23 | } 24 | ``` 25 | 26 | style.rtl.css 27 | 28 | ```css 29 | .example { 30 | direction: rtl; 31 | margin: 0; 32 | padding: 1em 1em .5em 2em; 33 | background-color: #353639; 34 | font-family: "Droid Arabic Kufi","Droid Sans", sans-serif; 35 | font-size: 14px; 36 | } 37 | ``` 38 | 39 | ### Features 😛 40 | 41 | - [x] Full [RTLCSS](http://rtlcss.com/learn/usage-guide/options) options, plugins & hooks support 42 | - [x] CSS modules & Code splitting compatible 43 | - [x] Source map for debugging 44 | - [x] Support Webpack v4 45 | 46 | ## Install 47 | 48 | ```cli 49 | npm install rtl-css-transform-webpack-plugin --save-dev 50 | ``` 51 | 52 | ## Usage 53 | 54 | ```js 55 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 56 | const RtlCssPlugin = require("rtl-css-transform-webpack-plugin"); 57 | 58 | module.exports = { 59 | entry: path.resolve(__dirname, "src", "index.js"), 60 | output: { 61 | path: path.resolve(__dirname, "dist"), 62 | filename: "[name].js", 63 | chunkFilename: "[name].js" // only for code splitting 64 | }, 65 | plugins: [ 66 | new MiniCssExtractPlugin(), 67 | new RtlCssPlugin() 68 | ], 69 | module: { 70 | rules: [ 71 | { 72 | test: /\.css$/, 73 | use: [ 74 | MiniCssExtractPlugin.loader, 75 | "css-loader" 76 | ] 77 | } 78 | ] 79 | } 80 | } 81 | ``` 82 | 83 | ### Filename 84 | 85 | This option determines the name of each output bundle. 86 | 87 | The default file name is ```[name].rtl.css```. 88 | 89 | See [webpack output filename](https://webpack.js.org/configuration/output/#output-filename) for more details. 90 | 91 | ```js 92 | new RtlCssPlugin({ 93 | filename: "rtl/[chunkhash].css" 94 | }) 95 | ``` 96 | 97 | ### Sourcemap 98 | 99 | Produces inline source map. Disabled by default. 100 | 101 | Source mapping can be enabled via ```sourcemap``` option. 102 | 103 | ```js 104 | new RtlCssPlugin({ 105 | sourcemap: true 106 | }) 107 | ``` 108 | 109 | Alternatively it can be enabled using [webpack devtool option](https://webpack.js.org/configuration/devtool/#devtool). 110 | 111 | ```cli 112 | webpack --devtool source-map 113 | ``` 114 | 115 | ### Minimize 116 | 117 | Using built-in webpack optimization via production option. 118 | 119 | ```cli 120 | webpack -p 121 | ``` 122 | 123 | Alternatively see [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/README.md#minimizing-for-production) optimization recommendation. 124 | 125 | ### RTLCSS options 126 | 127 | RTLCSS usage can be customized using object config. 128 | 129 | Options are same as [RTLCSS options](http://rtlcss.com/learn/usage-guide/options). 130 | Plugins are same as [RTLCSS plugins](http://rtlcss.com/learn/extending-rtlcss/writing-a-plugin). 131 | Hooks are same as [RTLCSS hooks](http://rtlcss.com/learn/usage-guide/hooks). 132 | 133 | ```js 134 | new RtlCssPlugin({ 135 | config: { 136 | options: {}, 137 | plugins: [], 138 | hooks: {} 139 | } 140 | }) 141 | ``` 142 | 143 | ---- 144 | 145 | 💐 Special thanks to @MohammadYounes for RTLCSS. --------------------------------------------------------------------------------