diff --git a/docs/css.md b/docs/css.md index 82580028d..a863929eb 100644 --- a/docs/css.md +++ b/docs/css.md @@ -4,7 +4,7 @@ Webpacker supports importing CSS, Sass and SCSS files directly into your JavaScript files. -## Import styles into your JS app +## Import global styles into your JS app ```sass // app/javascript/hello_react/styles/hello-react.sass @@ -30,6 +30,36 @@ const Hello = props => ( ) ``` +## Import scoped styles into your JS app + +Stylesheets end with '.modules.*' is treated as [CSS Modules](https://github.com/css-modules/css-modules). + +```sass +// app/javascript/hello_react/styles/hello-react.module.sass + +.helloReact + padding: 20px + font-size: 12px +``` + +```js +// React component example +// app/javascripts/packs/hello_react.jsx + +import React from 'react' +import helloIcon from '../hello_react/images/icon.png' +import styles from '../hello_react/styles/hello-react' + +const Hello = props => ( +
+ hello-icon +

Hello {props.name}!

+
+) +``` + +**Note:** Declared class is referenced as object property in JavaScript. + ## Link styles from your Rails views diff --git a/lib/install/config/webpacker.yml b/lib/install/config/webpacker.yml index d3f24e1b4..6da493bcb 100644 --- a/lib/install/config/webpacker.yml +++ b/lib/install/config/webpacker.yml @@ -18,6 +18,9 @@ default: &default - .sass - .scss - .css + - .module.sass + - .module.scss + - .module.css - .png - .svg - .gif diff --git a/package/__tests__/config.js b/package/__tests__/config.js index 712ffc178..e5b6b94cc 100644 --- a/package/__tests__/config.js +++ b/package/__tests__/config.js @@ -15,6 +15,9 @@ describe('Webpacker.yml config', () => { '.sass', '.scss', '.css', + '.module.sass', + '.module.scss', + '.module.css', '.png', '.svg', '.gif', diff --git a/package/rules/css.js b/package/rules/css.js index 521f21d51..8354040b8 100644 --- a/package/rules/css.js +++ b/package/rules/css.js @@ -1,39 +1,7 @@ -const ExtractTextPlugin = require('extract-text-webpack-plugin') -const path = require('path') -const { dev_server: devServer } = require('../config') +const styleRuleFactory = require('./style_rule_factory') -const postcssConfigPath = path.resolve(process.cwd(), '.postcssrc.yml') -const isProduction = process.env.NODE_ENV === 'production' -const inDevServer = process.argv.find(v => v.includes('webpack-dev-server')) -const isHMR = inDevServer && (devServer && devServer.hmr) -const extractCSS = !(isHMR) || isProduction - -const styleLoader = { - loader: 'style-loader', - options: { - hmr: isHMR, - sourceMap: true - } -} - -const extractOptions = { - fallback: styleLoader, - use: [ - { loader: 'css-loader', options: { minimize: isProduction, sourceMap: true, importLoaders: 2 } }, - { loader: 'postcss-loader', options: { sourceMap: true, config: { path: postcssConfigPath } } } - ] -} - -// For production extract styles to a separate bundle -const extractCSSLoader = { - test: /\.(css)$/i, - use: ExtractTextPlugin.extract(extractOptions) -} - -// For hot-reloading use regular loaders -const inlineCSSLoader = { - test: /\.(css)$/i, - use: [styleLoader].concat(extractOptions.use) -} - -module.exports = extractCSS ? extractCSSLoader : inlineCSSLoader +module.exports = styleRuleFactory( + /\.(css)$/i, + false, + [] +) diff --git a/package/rules/index.js b/package/rules/index.js index 7784dd9cc..1a4ea3c2a 100644 --- a/package/rules/index.js +++ b/package/rules/index.js @@ -2,10 +2,14 @@ const babel = require('./babel') const file = require('./file') const css = require('./css') const sass = require('./sass') +const moduleCss = require('./module.css') +const moduleSass = require('./module.sass') module.exports = { babel, css, sass, + moduleCss, + moduleSass, file } diff --git a/package/rules/module.css.js b/package/rules/module.css.js new file mode 100644 index 000000000..6671d8548 --- /dev/null +++ b/package/rules/module.css.js @@ -0,0 +1,7 @@ +const styleRuleFactory = require('./style_rule_factory') + +module.exports = styleRuleFactory( + /\.(css)$/i, + true, + [] +) diff --git a/package/rules/module.sass.js b/package/rules/module.sass.js new file mode 100644 index 000000000..9a8f01a4f --- /dev/null +++ b/package/rules/module.sass.js @@ -0,0 +1,10 @@ +const styleRuleFactory = require('./style_rule_factory') + +module.exports = styleRuleFactory( + /\.(scss|sass)$/i, + true, + [{ + loader: 'sass-loader', + options: { sourceMap: true } + }] +) diff --git a/package/rules/sass.js b/package/rules/sass.js index 9a8107a2c..41cec0899 100644 --- a/package/rules/sass.js +++ b/package/rules/sass.js @@ -1,15 +1,10 @@ -const cssLoader = require('./css') -const deepMerge = require('../utils/deep_merge') +const styleRuleFactory = require('./style_rule_factory') -// Duplicate and remove css loader object reference -let sassLoader = JSON.parse(JSON.stringify(cssLoader)) - -sassLoader = deepMerge(sassLoader, { - test: /\.(scss|sass)$/i, - use: [{ +module.exports = styleRuleFactory( + /\.(scss|sass)$/i, + false, + [{ loader: 'sass-loader', options: { sourceMap: true } }] -}) - -module.exports = sassLoader +) diff --git a/package/rules/style_rule_factory.js b/package/rules/style_rule_factory.js new file mode 100644 index 000000000..a6d9bf6a5 --- /dev/null +++ b/package/rules/style_rule_factory.js @@ -0,0 +1,60 @@ +const ExtractTextPlugin = require('extract-text-webpack-plugin') +const path = require('path') +const { dev_server: devServer } = require('../config') + +const postcssConfigPath = path.resolve(process.cwd(), '.postcssrc.yml') +const isProduction = process.env.NODE_ENV === 'production' +const inDevServer = process.argv.find(v => v.includes('webpack-dev-server')) +const isHMR = inDevServer && (devServer && devServer.hmr) +const extractCSS = !(isHMR) || isProduction + +const styleRuleFactory = (test, modules, preprocessors) => { + const styleLoader = { + loader: 'style-loader', + options: { + hmr: isHMR, + sourceMap: true + } + } + + const extractOptions = { + fallback: styleLoader, + use: [ + { + loader: 'css-loader', + options: { + minimize: isProduction, sourceMap: true, importLoaders: 2, modules + } + }, + { + loader: 'postcss-loader', + options: { + sourceMap: true, config: { path: postcssConfigPath } + } + }, + ...preprocessors + ] + } + + const modulesCondition = modules + ? { include: /\.module\.[a-z]+$/ } + : { exclude: /\.module\.[a-z]+$/ } + + // For production extract styles to a separate bundle + const extractCSSLoader = Object.assign( + {}, + { test, use: ExtractTextPlugin.extract(extractOptions) }, + modulesCondition + ) + + // For hot-reloading use regular loaders + const inlineCSSLoader = Object.assign( + {}, + { test, use: [styleLoader].concat(extractOptions.use) }, + modulesCondition + ) + + return extractCSS ? extractCSSLoader : inlineCSSLoader +} + +module.exports = styleRuleFactory diff --git a/test/test_app/config/webpacker.yml b/test/test_app/config/webpacker.yml index c2a2a46c6..beaf1831c 100644 --- a/test/test_app/config/webpacker.yml +++ b/test/test_app/config/webpacker.yml @@ -20,6 +20,9 @@ default: &default - .sass - .scss - .css + - .module.sass + - .module.scss + - .module.css - .png - .svg - .gif