rework-webpack-loader
v0.2.0
Published
rework loader for webpack
Downloads
64
Readme
rework webpack loader
this is a loader for webpack that allows you to create css modules and process them with rework.
it's different than the other rework webpack loader because it treats each css file as a different webpack module.
usage
if you're not familiar with how webpack loaders work, you should checkout the main documentation first. webpack loaders allow you to transform the source of webpack modules.
rework allows you to create modular css transformations.
rework-webpack-loader
works in conjunction with
style-loader
, but it replaces the standard
css-loader
.
installation
$ npm install --save-dev rework-webpack-loader
configuration
you can apply rework-webpack-loader
to your css either explicitly in the
require
statements:
require('style!rework-webpack!./index.css');
or you can apply it to all css files via a configuration file:
var reworkLoader = require('rework-webpack-loader');
var reworkCalc = require('rework-calc');
module.exports = {
module: {
loaders: [
{test: /\.css$/, loader: 'style-loader!rework-webpack-loader'}
// ...
]
},
// the rest of the config...
rework: {
use [
reworkCalc
// ... other rework plugins ...
]
}
};
advanced configuration example
rework-webpack-loader
has to do some tricky things to get @import
's, url's,
and variables working correctly. so a full featured config might look like
this:
var reworkLoader = require('rework-webpack-loader');
var reworkVars = require('rework-vars');
var reworkCalc = require('rework-calc');
var reworkCustomMedia = require('rework-custom-media');
// if you want to have something like a theme file that can override the css
// variables defined directly in the css file, make a variable map.
var varMap = reworkLoader.makeVarMap('src/index.css');
module.exports = {
resolve: {
// if you want to @import stuff you installed with npm, such as suit css,
// you probably need to include `style` in your `packageMains` config
packageMains: [
'webpack',
'browser',
'web',
'browserify',
['jam', 'main'],
'style',
'main'
]
// ...
},
module: {
loaders: [
{test: /\.css$/, loader: 'style-loader!rework-webpack-loader'}
// ...
]
},
// ...
rework: {
use: [
reworkLoader.plugins.imports,
reworkLoader.plugins.urls,
reworkLoader.plugins.stripLocalDefs(varMap),
reworkCustomMedia({map: varMap}),
reworkVars({map: varMap}),
reworkCalc
]
}
};
plugin api
the plugin api is the same as rework's, with a couple additions:
- async plugins: if you retur a promise from a plugin,
rework-webpack-loader
will wait for it to complete before applying the next plugin. - process the resulting JS:
rework-webpack-loader
works similarly to the defaultcss-loader
in that it transforms css into a javascript webpack module that just exports a string of css. in order for url's in the css to resolve correctly, the plugin needs to be able to edit the resulting js, not just the css, so if plugins define amodule.exports.processJs
function, they have the opportunity to transform the resulting javascript. check out the urls plugin for an example.
motivation
the default css-loader
plugin wasn't enough for my needs because i wanted
features like css variables and the calc
method.
the issue with the existing rework-loader
is it doesn't treat each css file
as a separate webpack module, so you lose all of webpack's dependency tracking
(a.k.a. the big motivator for using a module system in the first place).
why are separate modules so important?
consider an app with 3 css files:
base.css
: css reset and utility classesmenu.css
: styles for a re-usable css drop-down menu, which@import
'sbase.css
since it builds on those utility classesapp.css
: styles specific to this application, which also@import
'sbase.css
since the app needs the utility classes and reset
while the last file is only ever going to be used for this app, there's a good chance you'll want to re-use the first two in other apps. so you'll also have a couple javascript modules:
menu.js
: a dropdown-menu javascript component whichrequire
'smenu.css
app.js
: the rest of your app code, which presumably uses the menu component
your dependency tree looks something like this:
css preprocessors that don't treat each css file as a separate module will end
up duplicating base.css
in the final output. this includes the
rework-loader
, less-loader
, and any others that inline @import
statements. while they de-dupe the files during the build, since menu.css
and
app.css
are different entry points, the preprocessor has no way of knowing
about the shared files. when they output the css to webpack, there is no simple
way of telling webpack which dependencies are included in the compiled module.
you also have the development-time annoyance that since webpack doesn't see
these dependencies, making a change to base.css
won't trigger a
re-compilation.
why not just chain plugins together?
one approach for solving the dependency issue would be to just compile the
source, but not trace the dependencies in the preprocessor. this could be done
in less by using a .css
extension in the @import
statements
or in rework by not including the import plugin.
now the problem becomes resolving variables. variables are especially hard to resolve at build time since they have a sort of cyclic dependency:
base.css
might define a--base-font
variable and use it to set the default font for the app (like in SUIT CSS, for example)- this variable is used in
base.css
, so we need to know it's value in order to compile thebase.css
module
- this variable is used in
app.css
might reference--base-font
, so it depends onbase.css
app.css
might also re-define--base-font
, so now in order to compilebase.css
, we need that new value. nowbase.css
depends onapp.css
this is only a problem because we're trying to resolve the values at build-time, but if we had css variable support in browsers it wouldn't be an issue (we don't though).
rework-webpack-loader
solves this by going through all of the css files up
front and creating a mapping from variable names to values, and then using this
to resolve variables during compilation. the downside is that if you change
variable values, you need to restart the webpack dev server, but the upside is
that it handles css variables' cyclic dependencies.
why couldn't you re-use the default css-loader
?
to avoid an extra parse of the css. a little background on how webpack makes css work by default:
- the default
css-loader
transforms css to a javascript module that:require
's all of the things the css@import
'ed- rewrites url's to the webpack output's chunk name
- url's are treated as relative to the css file's location
- if they reference, say, an image, the final css will reference webpack's
name for the image, which is a content hash
- this enables long term caching of images, fonts, etc.
- returns a string of css
- the
style-loader
takes the name of a webpack module that returns a string, and creates a module that inserts a<style>
element into the document with the css string as its content
if we re-use css-loader
we're parsing/stringifying the css twice. admittedly
this is kind of a weak reason. especially since, if you want to add something
like autoprefixer
, you're going to have to parse the css
again anyway. you're probably better off optimizing the development build by
focusing on incrementally building small modules anyway.