├── .gitignore ├── .babelrc ├── .npmignore ├── test ├── rtl-diff-result.css ├── src │ ├── index.js │ └── index.css └── index.js ├── .travis.yml ├── .editorconfig ├── package.json ├── LICENSE.md ├── README.md └── src └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | test/dist* 2 | node_modules 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test 3 | .babelrc 4 | .editorconfig 5 | .flowconfig 6 | .gitignore 7 | .travis.yml 8 | -------------------------------------------------------------------------------- /test/rtl-diff-result.css: -------------------------------------------------------------------------------- 1 | .foo { 2 | padding-right: 10px; 3 | } 4 | .bar { 5 | left: 100px; 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8.9.1" 4 | 5 | script: 6 | - npm test 7 | 8 | notifications: 9 | email: false 10 | -------------------------------------------------------------------------------- /test/src/index.js: -------------------------------------------------------------------------------- 1 | require('./index.css') 2 | 3 | module.exports = function() { 4 | return 'Treacherous this deceit to make no choice matter' 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /test/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --brightest: #fff; 3 | } 4 | 5 | body { 6 | background: var(--brightest); 7 | direction: lrt; 8 | } 9 | 10 | .foo { 11 | padding-left: 10px; 12 | } 13 | 14 | .bar { 15 | position: absolute; 16 | right: 100px; 17 | } 18 | 19 | .prev { 20 | width: 10px; 21 | } 22 | 23 | .foo .bar { 24 | height: 10px; 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-rtl-plugin", 3 | "version": "2.0.0", 4 | "description": "Webpack plugin to produce a rtl css bundle", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "rm -rf test/dist* && mocha test/*.js" 8 | }, 9 | "author": "Romain Berger ", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/romainberger/webpack-rtl-plugin" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/romainberger/webpack-rtl-plugin" 16 | }, 17 | "homepage": "https://github.com/romainberger/webpack-rtl-plugin", 18 | "keywords": [ 19 | "webpack", 20 | "css", 21 | "rtl" 22 | ], 23 | "license": "MIT", 24 | "devDependencies": { 25 | "chai": "4.2.0", 26 | "css-loader": "2.1.1", 27 | "mini-css-extract-plugin": "0.5.0", 28 | "mocha": "6.0.2", 29 | "webpack": "4.29.6" 30 | }, 31 | "dependencies": { 32 | "@romainberger/css-diff": "^1.0.3", 33 | "async": "^2.0.0", 34 | "cssnano": "4.1.10", 35 | "rtlcss": "2.4.0", 36 | "webpack-sources": "1.3.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Romain Berger 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Webpack RTL Plugin [![Build Status](https://img.shields.io/travis/romainberger/webpack-rtl-plugin/master.svg?style=flat-square)](https://travis-ci.org/romainberger/webpack-rtl-plugin) [![npm version](https://img.shields.io/npm/v/webpack-rtl-plugin.svg?style=flat-square)](https://www.npmjs.com/package/webpack-rtl-plugin) [![npm downloads](https://img.shields.io/npm/dm/webpack-rtl-plugin.svg?style=flat-square)](https://www.npmjs.com/package/webpack-rtl-plugin) 2 | 3 | Webpack plugin to use in addition to [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) to create a second css bundle, processed to be rtl. 4 | 5 | This uses [rtlcss](https://github.com/MohammadYounes/rtlcss) under the hood, please refer to its documentation for supported properties. 6 | 7 | Check out the [webpack-rtl-example](https://github.com/romainberger/webpack-rtl-example) to see an example of an app using the rtl-css-loader and webpack-rtl-plugin. 8 | 9 | ## Installation 10 | 11 | ```shell 12 | $ npm install webpack-rtl-plugin 13 | ``` 14 | 15 | ## Usage 16 | 17 | Add the plugin to your webpack configuration: 18 | 19 | ```js 20 | const WebpackRTLPlugin = require('webpack-rtl-plugin') 21 | 22 | module.exports = { 23 | entry: path.join(__dirname, 'src/index.js'), 24 | output: { 25 | path: path.resolve(__dirname, 'dist'), 26 | filename: 'bundle.js', 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.css$/, 32 | use: [ 33 | MiniCssExtractPlugin.loader, 34 | { 35 | loader: "css-loader", 36 | options: { 37 | ..., 38 | } 39 | } 40 | ] 41 | } 42 | ], 43 | }, 44 | plugins: [ 45 | new MiniCssExtractPlugin({ 46 | filename: 'style.css', 47 | }), 48 | new WebpackRTLPlugin(), 49 | ], 50 | } 51 | ``` 52 | 53 | This will create the normal `style.css` and an additionnal `style.rtl.css`. 54 | 55 | ## Options 56 | 57 | ``` 58 | new WebpackRTLPlugin({ 59 | filename: 'style.[contenthash].rtl.css', 60 | options: {}, 61 | plugins: [], 62 | diffOnly: false, 63 | minify: true, 64 | }) 65 | ``` 66 | 67 | * `test` a RegExp (object or string) that must match asset filename 68 | * `filename` the filename of the result file. May contain patterns in brackets. Default to `style.css`. 69 | * `[contenthash]` a hash of the content of the extracted file 70 | * `[id]` the module identifier 71 | * `[name]` the module name 72 | * `[file]` the extracted file filename 73 | * `[filebase]` the extracted file basename 74 | * `[ext]` the extracted file extension 75 | * May be an array of replace function arguments like `[/(\.css)/i, '-rtl$1']`. 76 | Replace applies to filename that specified in extract-text-webpack-plugin. 77 | * `options` Options given to `rtlcss`. See the [rtlcss documentation for available options](http://rtlcss.com/learn/usage-guide/options/). 78 | * `plugins` RTLCSS plugins given to `rtlcss`. See the [rtlcss documentation for writing plugins](http://rtlcss.com/learn/extending-rtlcss/writing-a-plugin/). Default to `[]`. 79 | * `diffOnly` If set to `true`, the stylesheet created will only contain the css that differs from the source stylesheet. Default to `false`. 80 | * `minify` will minify the css. You can also pass an object for the arguments passed to `cssnano`. Default to `true`. 81 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { createHash } = require('crypto') 3 | const rtlcss = require('rtlcss') 4 | const { ConcatSource } = require('webpack-sources') 5 | const cssDiff = require('@romainberger/css-diff') 6 | const { forEachOfLimit } = require('async') 7 | const cssnano = require('cssnano') 8 | 9 | const pluginName = "WebpackRTLPlugin" 10 | 11 | class WebpackRTLPlugin { 12 | constructor(options) { 13 | this.options = { 14 | filename: false, 15 | options: {}, 16 | plugins: [], 17 | ...options 18 | } 19 | } 20 | 21 | apply(compiler) { 22 | compiler.hooks.emit.tapAsync(pluginName, (compilation, callback) => { 23 | forEachOfLimit(compilation.chunks, 5, (chunk, key, cb) => { 24 | const rtlFiles = [] 25 | let cssnanoPromise = Promise.resolve() 26 | 27 | chunk.files.forEach(asset => { 28 | const match = this.options.test ? new RegExp(this.options.test).test(asset) : true 29 | 30 | if (path.extname(asset) !== '.css') { 31 | return 32 | } 33 | 34 | const baseSource = compilation.assets[asset].source() 35 | let filename 36 | let rtlSource 37 | 38 | if (match) { 39 | rtlSource = rtlcss.process(baseSource, this.options.options, this.options.plugins) 40 | 41 | if (this.options.filename instanceof Array && this.options.filename.length === 2) { 42 | filename = asset.replace(this.options.filename[0], this.options.filename[1]) 43 | } 44 | else if (this.options.filename) { 45 | filename = this.options.filename 46 | 47 | if (/\[contenthash]/.test(this.options.filename)) { 48 | const hash = createHash('md5').update(rtlSource).digest('hex').substr(0, 10) 49 | filename = filename.replace('[contenthash]', hash) 50 | } 51 | if (/\[id]/.test(this.options.filename)) { 52 | filename = filename.replace('[id]', chunk.id) 53 | } 54 | if (/\[name]/.test(this.options.filename)) { 55 | filename = filename.replace('[name]', chunk.name) 56 | } 57 | if (/\[file]/.test(this.options.filename)) { 58 | filename = filename.replace('[file]', asset) 59 | } 60 | if (/\[filebase]/.test(this.options.filename)) { 61 | filename = filename.replace('[filebase]', path.basename(asset)) 62 | } 63 | if (/\[ext]/.test(this.options.filename)) { 64 | filename = filename.replace('.[ext]', path.extname(asset)) 65 | } 66 | } 67 | else { 68 | const newFilename = `${path.basename(asset, '.css')}.rtl` 69 | filename = asset.replace(path.basename(asset, '.css'), newFilename) 70 | } 71 | 72 | if (this.options.diffOnly) { 73 | rtlSource = cssDiff(baseSource, rtlSource) 74 | } 75 | } 76 | 77 | if (this.options.minify !== false) { 78 | let nanoOptions = { from: undefined } 79 | if (typeof this.options.minify === 'object') { 80 | nanoOptions = this.options.minify 81 | } 82 | 83 | cssnanoPromise = cssnanoPromise.then(() => { 84 | let minify = cssnano.process( baseSource, nanoOptions).then(output => { 85 | compilation.assets[asset] = new ConcatSource(output.css) 86 | }); 87 | 88 | if (match) { 89 | const rtlMinify = cssnano.process(rtlSource, nanoOptions).then(output => { 90 | compilation.assets[filename] = new ConcatSource(output.css) 91 | rtlFiles.push(filename) 92 | }); 93 | 94 | minify = Promise.all([minify, rtlMinify]); 95 | } 96 | 97 | return minify; 98 | }) 99 | } 100 | else if (match) { 101 | compilation.assets[filename] = new ConcatSource(rtlSource) 102 | rtlFiles.push(filename) 103 | } 104 | }) 105 | 106 | cssnanoPromise.then(() => { 107 | chunk.files.push.apply(chunk.files, rtlFiles) 108 | cb() 109 | }) 110 | }, callback) 111 | }) 112 | } 113 | } 114 | 115 | module.exports = WebpackRTLPlugin 116 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const { expect } = require('chai') 4 | const webpack = require('webpack') 5 | const WebpackRTLPlugin = require('../src') 6 | const MiniCssExtractPlugin = require("mini-css-extract-plugin") 7 | 8 | const baseConfig = { 9 | mode: "development", 10 | entry: path.join(__dirname, 'src/index.js'), 11 | output: { 12 | path: path.resolve(__dirname, 'dist'), 13 | filename: 'bundle.js', 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.css$/, 19 | use: [ 20 | MiniCssExtractPlugin.loader, 21 | { 22 | loader: "css-loader", 23 | options: { 24 | modules: true, 25 | url: false, 26 | importLoaders: 1, 27 | localIdentName: "[local]", 28 | }, 29 | } 30 | ], 31 | } 32 | ], 33 | }, 34 | plugins: [ 35 | new MiniCssExtractPlugin({ 36 | filename: 'style.css', 37 | }), 38 | new WebpackRTLPlugin({ 39 | minify: false, 40 | }), 41 | ], 42 | } 43 | 44 | describe('Webpack RTL Plugin', () => { 45 | it('should export a function', () => { 46 | expect(WebpackRTLPlugin).to.be.a('function') 47 | expect(require('../')).to.be.a('function') 48 | }) 49 | 50 | const bundlePath = path.join(__dirname, 'dist/bundle.js') 51 | const cssBundlePath = path.join(__dirname, 'dist/style.css') 52 | const rtlCssBundlePath = path.join(__dirname, 'dist/style.rtl.css') 53 | 54 | describe('Bundling', () => { 55 | before(done => { 56 | webpack(baseConfig, (err, stats) => { 57 | if (err) { 58 | return done(err) 59 | } 60 | 61 | if (stats.hasErrors()) { 62 | return done(new Error(stats.toString())) 63 | } 64 | 65 | done() 66 | }) 67 | }) 68 | 69 | it('should create a second bundle', () => { 70 | expect(fs.existsSync(bundlePath)).to.be.true 71 | expect(fs.existsSync(cssBundlePath)).to.be.true 72 | expect(fs.existsSync(rtlCssBundlePath)).to.be.true 73 | }) 74 | 75 | it('should contain the correct content', () => { 76 | const contentCss = fs.readFileSync(cssBundlePath, 'utf-8') 77 | const contentRrlCss = fs.readFileSync(rtlCssBundlePath, 'utf-8') 78 | 79 | expect(contentCss).to.contain('padding-left: 10px;') 80 | expect(contentRrlCss).to.contain('padding-right: 10px;') 81 | }) 82 | }) 83 | 84 | describe('Test option', () => { 85 | let bundlePath 86 | let cssBundlePath 87 | let rtlCssBundlePath 88 | 89 | before(done => { 90 | const config = { 91 | ...baseConfig, 92 | entry: { 93 | 'js/main.js': path.join(__dirname, 'src/index.js'), 94 | }, 95 | output: { 96 | path: path.resolve(__dirname, 'dist-test'), 97 | filename: '[name]', 98 | }, 99 | plugins: [ 100 | new MiniCssExtractPlugin({ 101 | filename: 'css/style.css', 102 | }), 103 | new WebpackRTLPlugin({ 104 | test: /css\//i, 105 | minify: false, 106 | }), 107 | ], 108 | } 109 | 110 | webpack(config, (err, stats) => { 111 | if (err) { 112 | return done(err) 113 | } 114 | 115 | if (stats.hasErrors()) { 116 | return done(new Error(stats.toString())) 117 | } 118 | 119 | bundlePath = path.join(__dirname, 'dist-test/js/main.js') 120 | cssBundlePath = path.join(__dirname, 'dist-test/css/style.css') 121 | rtlCssBundlePath = path.join(__dirname, 'dist-test/css/style.rtl.css') 122 | 123 | done() 124 | }) 125 | }) 126 | 127 | it('should create a two css bundles', () => { 128 | expect(fs.existsSync(bundlePath)).to.be.true 129 | expect(fs.existsSync(cssBundlePath)).to.be.true 130 | expect(fs.existsSync(rtlCssBundlePath)).to.be.true 131 | }) 132 | }) 133 | 134 | describe('Filename options', () => { 135 | let cssBundleName 136 | let rtlCssBundleName 137 | let cssBundlePath 138 | let rtlCssBundlePath 139 | 140 | before(done => { 141 | const config = { 142 | ...baseConfig, 143 | output: { 144 | path: path.resolve(__dirname, 'dist-hash'), 145 | filename: 'bundle.js', 146 | }, 147 | plugins: [ 148 | new MiniCssExtractPlugin({ 149 | filename: 'style.[contenthash].css', 150 | }), 151 | new WebpackRTLPlugin({ 152 | filename: 'style.[contenthash].rtl.css', 153 | minify: false, 154 | }), 155 | ], 156 | } 157 | 158 | webpack(config, (err, stats) => { 159 | if (err) { 160 | return done(err) 161 | } 162 | 163 | if (stats.hasErrors()) { 164 | return done(new Error(stats.toString())) 165 | } 166 | 167 | Object.keys(stats.compilation.assets).forEach(asset => { 168 | const chunk = asset.split('.') 169 | 170 | if (path.extname(asset) === '.css') { 171 | if (chunk[chunk.length - 2] === 'rtl') { 172 | rtlCssBundleName = asset 173 | rtlCssBundlePath = path.join(__dirname, 'dist-hash', asset) 174 | } 175 | else { 176 | cssBundleName = asset 177 | cssBundlePath = path.join(__dirname, 'dist-hash', asset) 178 | } 179 | } 180 | }) 181 | 182 | done() 183 | }) 184 | }) 185 | 186 | it('should create a two css bundles', () => { 187 | expect(fs.existsSync(cssBundlePath)).to.be.true 188 | expect(fs.existsSync(rtlCssBundlePath)).to.be.true 189 | }) 190 | 191 | it('should create a second bundle with a different hash', () => { 192 | const cssChunk = cssBundleName.split('.') 193 | const rtlCssChunk = rtlCssBundleName.split('.') 194 | 195 | expect(cssChunk[1]).to.not.equal(rtlCssChunk[1]) 196 | }) 197 | }) 198 | 199 | describe('Filename options with patterns', () => { 200 | let cssBundleName 201 | let rtlCssBundleName 202 | let cssBundlePath 203 | let rtlCssBundlePath 204 | 205 | before(done => { 206 | const config = { 207 | ...baseConfig, 208 | output: { 209 | path: path.resolve(__dirname, 'dist-patterns'), 210 | filename: 'bundle.js', 211 | }, 212 | plugins: [ 213 | new MiniCssExtractPlugin({ 214 | filename: 'style.[contenthash].css', 215 | }), 216 | new WebpackRTLPlugin({ 217 | filename: '[id]-[file]-[contenthash]-[name]-[filebase].rtl.[ext]', 218 | minify: false, 219 | }), 220 | ], 221 | } 222 | 223 | webpack(config, (err, stats) => { 224 | if (err) { 225 | return done(err) 226 | } 227 | 228 | if (stats.hasErrors()) { 229 | return done(new Error(stats.toString())) 230 | } 231 | 232 | Object.keys(stats.compilation.assets).forEach(asset => { 233 | const chunk = asset.split('.') 234 | 235 | if (path.extname(asset) === '.css') { 236 | if (chunk[chunk.length - 2] === 'rtl') { 237 | rtlCssBundleName = asset 238 | rtlCssBundlePath = path.join(__dirname, 'dist-patterns', asset) 239 | } 240 | else { 241 | cssBundleName = asset 242 | cssBundlePath = path.join(__dirname, 'dist-patterns', asset) 243 | } 244 | } 245 | }) 246 | 247 | done() 248 | }) 249 | }) 250 | 251 | it('should create a two css bundles', () => { 252 | expect(fs.existsSync(cssBundlePath)).to.be.true 253 | expect(fs.existsSync(rtlCssBundlePath)).to.be.true 254 | }) 255 | 256 | it('should create a second bundle with a different hash', () => { 257 | const cssChunk = cssBundleName.split('.')[1] 258 | const rtlCssChunk = rtlCssBundleName.split('-')[2] 259 | 260 | expect(cssChunk).to.not.equal(rtlCssChunk) 261 | }) 262 | }) 263 | 264 | describe('Filename options with replace array', () => { 265 | let cssBundleName 266 | let rtlCssBundleName 267 | let cssBundlePath 268 | let rtlCssBundlePath 269 | 270 | before(done => { 271 | const config = { 272 | ...baseConfig, 273 | output: { 274 | path: path.resolve(__dirname, 'dist-replace'), 275 | filename: 'bundle.js', 276 | }, 277 | plugins: [ 278 | new MiniCssExtractPlugin({ 279 | filename: 'style.[contenthash].css', 280 | }), 281 | new WebpackRTLPlugin({ 282 | filename: [/(\.css)/, '-rtl$1'], 283 | minify: false, 284 | }), 285 | ], 286 | } 287 | 288 | webpack(config, (err, stats) => { 289 | if (err) { 290 | return done(err) 291 | } 292 | 293 | if (stats.hasErrors()) { 294 | return done(new Error(stats.toString())) 295 | } 296 | 297 | Object.keys(stats.compilation.assets).forEach(asset => { 298 | if (path.extname(asset) === '.css') { 299 | if (asset.substr(-7, 3) === 'rtl') { 300 | rtlCssBundleName = asset 301 | rtlCssBundlePath = path.join(__dirname, 'dist-replace', asset) 302 | } 303 | else { 304 | cssBundleName = asset 305 | cssBundlePath = path.join(__dirname, 'dist-replace', asset) 306 | } 307 | } 308 | }) 309 | 310 | done() 311 | }) 312 | }) 313 | 314 | it('should create a two css bundles', () => { 315 | expect(fs.existsSync(cssBundlePath)).to.be.true 316 | expect(fs.existsSync(rtlCssBundlePath)).to.be.true 317 | }) 318 | }) 319 | 320 | describe('Same path when no filename option', () => { 321 | let cssBundlePath 322 | let rtlCssBundlePath 323 | let cssPath = 'assets/css' 324 | 325 | before(done => { 326 | const config = { 327 | ...baseConfig, 328 | output: { 329 | path: path.resolve(__dirname, 'dist-path'), 330 | filename: 'bundle.js', 331 | }, 332 | plugins: [ 333 | new MiniCssExtractPlugin({ 334 | filename: path.join(cssPath, 'style.css'), 335 | }), 336 | new WebpackRTLPlugin(), 337 | ], 338 | } 339 | 340 | webpack(config, (err, stats) => { 341 | if (err) { 342 | return done(err) 343 | } 344 | 345 | if (stats.hasErrors()) { 346 | return done(new Error(stats.toString())) 347 | } 348 | 349 | cssBundlePath = path.join(__dirname, 'dist-path', cssPath, 'style.css') 350 | rtlCssBundlePath = path.join(__dirname, 'dist-path', cssPath, 'style.rtl.css') 351 | 352 | done() 353 | }) 354 | }) 355 | 356 | it('should create two css bundles with same path', () => { 357 | expect(fs.existsSync(cssBundlePath)).to.be.true 358 | expect(fs.existsSync(rtlCssBundlePath)).to.be.true 359 | }) 360 | }) 361 | 362 | describe('Rtlcss options', () => { 363 | const rtlCssBundlePath = path.join(__dirname, 'dist-options/style.rtl.css') 364 | 365 | before(done => { 366 | const config = { 367 | ...baseConfig, 368 | output: { 369 | path: path.resolve(__dirname, 'dist-options'), 370 | filename: 'bundle.js', 371 | }, 372 | plugins: [ 373 | new MiniCssExtractPlugin({ 374 | filename: 'style.css', 375 | }), 376 | new WebpackRTLPlugin({ 377 | options: { 378 | autoRename: true, 379 | stringMap: [ 380 | { 381 | search: 'prev', 382 | replace: 'next', 383 | options: { 384 | scope: '*', 385 | }, 386 | }, 387 | ], 388 | }, 389 | minify: false, 390 | }), 391 | ], 392 | } 393 | 394 | webpack(config, (err, stats) => { 395 | if (err) { 396 | return done(err) 397 | } 398 | 399 | if (stats.hasErrors()) { 400 | return done(new Error(stats.toString())) 401 | } 402 | 403 | done() 404 | }) 405 | }) 406 | 407 | it('should follow the options given to rtlcss', () => { 408 | const contentRrlCss = fs.readFileSync(rtlCssBundlePath, 'utf-8') 409 | expect(contentRrlCss).to.contain('.next {') 410 | }) 411 | }) 412 | 413 | describe('Rtlcss plugins', () => { 414 | const rtlCssBundlePath = path.join(__dirname, 'dist-options/style.rtl.css') 415 | 416 | before(done => { 417 | const config = { 418 | ...baseConfig, 419 | output: { 420 | path: path.resolve(__dirname, 'dist-options'), 421 | filename: 'bundle.js', 422 | }, 423 | plugins: [ 424 | new MiniCssExtractPlugin({ 425 | filename: 'style.css', 426 | }), 427 | new WebpackRTLPlugin({ 428 | plugins: [ 429 | // Based on github.com/MohammadYounes/rtlcss/issues/86#issuecomment-261875443 430 | { 431 | name: 'Skip variables', 432 | priority: 1, 433 | directives: { control: {}, value: [] }, 434 | processors: [ 435 | { 436 | name: '--', 437 | expr: /^--/im, 438 | action: (prop, value) => ({ prop, value }), 439 | }, 440 | ], 441 | }, 442 | ], 443 | minify: false, 444 | }), 445 | ], 446 | } 447 | 448 | webpack(config, (err, stats) => { 449 | if (err) { 450 | return done(err) 451 | } 452 | 453 | if (stats.hasErrors()) { 454 | return done(new Error(stats.toString())) 455 | } 456 | 457 | done() 458 | }) 459 | }) 460 | 461 | it('should follow the plugins given to rtlcss', () => { 462 | const contentRrlCss = fs.readFileSync(rtlCssBundlePath, 'utf-8') 463 | expect(contentRrlCss).to.contain('brightest') 464 | }) 465 | }) 466 | 467 | describe('Diff', () => { 468 | const rtlCssBundlePath = path.join(__dirname, 'dist-diff/style.rtl.css') 469 | 470 | before(done => { 471 | const config = { 472 | ...baseConfig, 473 | output: { 474 | path: path.resolve(__dirname, 'dist-diff'), 475 | filename: 'bundle.js', 476 | }, 477 | plugins: [ 478 | new MiniCssExtractPlugin({ 479 | filename: 'style.css', 480 | }), 481 | new WebpackRTLPlugin({ 482 | diffOnly: true, 483 | minify: false, 484 | }), 485 | ], 486 | } 487 | 488 | webpack(config, (err, stats) => { 489 | if (err) { 490 | return done(err) 491 | } 492 | 493 | if (stats.hasErrors()) { 494 | return done(new Error(stats.toString())) 495 | } 496 | 497 | done() 498 | }) 499 | }) 500 | 501 | it('should only contain the diff between the source and the rtl version', () => { 502 | const contentRrlCss = fs.readFileSync(rtlCssBundlePath, 'utf-8').replace(/\r/g, '') 503 | const expected = fs.readFileSync(path.join(__dirname, 'rtl-diff-result.css'), 'utf-8').replace(/\r/g, '') 504 | expect(contentRrlCss).to.equal(expected) 505 | }) 506 | }) 507 | 508 | describe('Minify', () => { 509 | const rtlCssBundlePath = path.join(__dirname, 'dist-min/style.rtl.css') 510 | 511 | before(done => { 512 | const config = { 513 | ...baseConfig, 514 | output: { 515 | path: path.resolve(__dirname, 'dist-min'), 516 | filename: 'bundle.js', 517 | }, 518 | plugins: [ 519 | new MiniCssExtractPlugin({ 520 | filename: 'style.css', 521 | }), 522 | new WebpackRTLPlugin(), 523 | ], 524 | } 525 | 526 | webpack(config, (err, stats) => { 527 | if (err) { 528 | return done(err) 529 | } 530 | 531 | if (stats.hasErrors()) { 532 | return done(new Error(stats.toString())) 533 | } 534 | 535 | done() 536 | }) 537 | }) 538 | 539 | it('should minify the css', () => { 540 | const contentRrlCss = fs.readFileSync(rtlCssBundlePath, 'utf-8') 541 | const expected = '.foo{padding-right:10px}.bar{position:absolute;left:100px}.prev{width:10px}.foo .bar{height:10px}' 542 | expect(contentRrlCss).to.contain(expected) 543 | }) 544 | }) 545 | }) 546 | --------------------------------------------------------------------------------