Skip to content

Commit 2de5396

Browse files
committed
feat(webpack): webpack@5 support
1 parent f443500 commit 2de5396

17 files changed

+1895
-5620
lines changed

package-lock.json

Lines changed: 738 additions & 3865 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"unist-builder": "^3.0.0",
8787
"unist-util-visit": "^4.1.0",
8888
"watchify": "^4.0.0",
89-
"webpack": "^4.30.0"
89+
"webpack": "^5.0.0"
9090
},
9191
"workspaces": [
9292
"./packages/*"

packages/webpack/README.md

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -70,49 +70,9 @@ All other options are passed to the underlying `Processor` instance, see [Option
7070

7171
### Loader Options
7272

73-
#### `defaultExport`
74-
75-
By default this plugin will create both a default export and named `export`s for each class in a CSS file. You can disable `default` by setting `defaultExport` to `false`.
76-
77-
```js
78-
...
79-
module : {
80-
rules : [{
81-
test : /\.css$/,
82-
use : {
83-
loader : "@modular-css/webpack/loader",
84-
options : {
85-
defaultExport : false
86-
}
87-
}
88-
}]
89-
},
90-
...
91-
```
92-
93-
#### `namedExports`
94-
95-
By default this plugin will create both a default export and named `export`s for each class in a CSS file. You can disable named `export`s by setting `namedExports` to `false`.
96-
97-
```js
98-
...
99-
module : {
100-
rules : [{
101-
test : /\.css$/,
102-
use : {
103-
loader : "@modular-css/webpack/loader",
104-
options : {
105-
namedExports : false
106-
}
107-
}
108-
}]
109-
},
110-
...
111-
```
112-
11373
#### `styleExport`
11474

115-
By default this plugin will export the style string, eg `import { styles } from "./style.css";`. You can disable this by setting `styleExport` to `false`.
75+
You can enable returning the style string, eg `import { styles } from "./style.css";`. by setting `styleExport` to `true`.
11676

11777
```js
11878
...
@@ -122,7 +82,7 @@ By default this plugin will export the style string, eg `import { styles } from
12282
use : {
12383
loader : "@modular-css/webpack/loader",
12484
options : {
125-
styleExport : false
85+
styleExport : true
12686
}
12787
}
12888
}]

packages/webpack/loader.js

Lines changed: 14 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
"use strict";
22

3-
const utils = require("loader-utils");
4-
const { keyword } = require("esutils");
3+
const utils = require("loader-utils");
54

6-
const output = require("@modular-css/processor/lib/output.js");
5+
const { transform } = require("@modular-css/css-to-js");
76

87
const DEFAULTS = {
98
styleExport : true,
@@ -14,57 +13,29 @@ const DEFAULTS = {
1413
// Can't be an arrow function due to `this` usage :(
1514
module.exports = async function(source) {
1615
const options = {
17-
__proto__ : null,
16+
__proto__ : null,
1817
...DEFAULTS,
1918
...utils.getOptions(this),
19+
20+
// Need this so webpack doesn't create terrible import
21+
// names that break snapshots
22+
relativeImports : true,
2023
};
2124

2225
const done = this.async();
23-
const processor = this.options ?
24-
// Webpack 2 & 3
25-
this.options.processor :
26-
// Webpack 4
27-
this._compiler.options.processor;
28-
29-
this.cacheable();
26+
const { processor } = this._compiler.options;
27+
const { resourcePath : file } = this;
3028

3129
try {
32-
const { details } = await processor.string(this.resourcePath, source);
33-
const exported = output.fileCompositions(details, processor, { joined : true });
34-
const values = output.values(details.values);
35-
36-
exported.$values = values;
37-
38-
const out = [];
39-
40-
if(options.defaultExport) {
41-
out.push(`export default ${JSON.stringify(exported, null, 4)};`);
42-
}
30+
await processor.string(file, source);
4331

44-
processor.fileDependencies(this.resourcePath).forEach(this.addDependency);
32+
const { code, warnings } = transform(file, processor, options);
4533

46-
// Just default object export in this case
47-
if(!options.namedExports) {
48-
return done(null, out.join("\n"));
49-
}
50-
51-
// Warn if any of the exported CSS wasn't able to be used as a valid JS identifier
52-
// and exclude from the output
53-
Object.keys(exported).forEach((ident) => {
54-
if(keyword.isReservedWordES6(ident) || !keyword.isIdentifierNameES6(ident)) {
55-
this.emitWarning(new Error(`Invalid JS identifier "${ident}", unable to export`));
56-
57-
return;
58-
}
59-
60-
out.push(`export var ${ident} = ${JSON.stringify(exported[ident])};`);
34+
warnings.forEach((warning) => {
35+
this.emitWarning(warning);
6136
});
6237

63-
if(options.styleExport) {
64-
out.push(`export var styles = ${JSON.stringify(details.result.css)};`);
65-
}
66-
67-
return done(null, out.join("\n"));
38+
return done(null, code);
6839
} catch(e) {
6940
return done(e);
7041
}

packages/webpack/package.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,11 @@
2828
"postcss"
2929
],
3030
"peerDependencies": {
31-
"webpack": "^4.30.0"
31+
"webpack": "^5.0.0"
3232
},
3333
"dependencies": {
34+
"@modular-css/css-to-js": "file:../css-to-js",
3435
"@modular-css/processor": "file:../processor",
35-
"esutils": "^2.0.2",
36-
"loader-utils": "^2.0.0",
37-
"lodash": "^4.17.0",
38-
"webpack-sources": "^2.0.0"
36+
"loader-utils": "^2.0.0"
3937
}
4038
}

packages/webpack/plugin.js

Lines changed: 71 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,84 @@
11
"use strict";
22

3-
const sources = require("webpack-sources");
4-
const ismap = require("lodash/isMap");
3+
const { sources } = require("webpack");
54

65
const Processor = require("@modular-css/processor");
76

8-
// Return a list of changed/removed files based on timestamp objects
9-
const getChangedFiles = (prev, curr) =>
10-
Object.keys(curr)
11-
.filter((file) =>
12-
!prev[file] || prev[file] < curr[file]
13-
)
14-
.concat(
15-
Object.keys(prev).filter((file) => !curr[file])
16-
);
7+
const PLUGIN_NAME = "@modular-css/webpack";
178

18-
function ModularCSS(args) {
19-
var options = {
20-
__proto__ : null,
21-
...args,
22-
};
9+
class ModularCSS {
10+
constructor(args) {
11+
var options = {
12+
__proto__ : null,
13+
...args,
14+
};
2315

24-
this.prev = {};
25-
this.processor = options.processor || new Processor(options);
26-
this.options = options;
27-
}
28-
29-
ModularCSS.prototype.apply = function(compiler) {
30-
var watching = false;
31-
32-
// File invalidated by webpack watcher
33-
compiler.plugin("invalid", (file) => {
34-
this.processor.remove(file);
35-
});
36-
37-
compiler.plugin("watch-run", (c, done) => {
38-
watching = true;
39-
40-
done();
41-
});
42-
43-
// Runs before compilation begins
44-
compiler.plugin("this-compilation", (compilation) => {
45-
var files;
46-
47-
// Make processor instance available to the loader
48-
compilation.options.processor = this.processor;
49-
50-
// This code is only useful when calling .run() multiple times
51-
// watching handles its own invalidations
52-
if(!watching) {
53-
let current;
54-
55-
if(ismap(compilation.fileTimestamps)) {
56-
current = {};
57-
58-
compilation.fileTimestamps.forEach((value, key) => (current[key] = value));
59-
}
16+
this.prev = {};
17+
this.processor = options.processor || new Processor(options);
18+
this.options = options;
19+
}
6020

61-
files = getChangedFiles(this.prev, current || compilation.fileTimestamps);
62-
63-
// Remove changed/removed files from processor instance
64-
this.processor.remove(files);
65-
66-
this.prev = compilation.fileTimestamps;
67-
}
68-
});
69-
70-
compiler.plugin("emit", async (compilation, done) => {
71-
// Don't even bother if errors happened
72-
if(compilation.errors.length) {
73-
return done();
74-
}
75-
76-
const data = await this.processor.output({
77-
to : this.options.css || false,
21+
apply(compiler) {
22+
// File invalidated by webpack watcher
23+
compiler.hooks.invalid.tap(PLUGIN_NAME, (file) => {
24+
this.processor.invalidate(file);
7825
});
7926

80-
if(this.options.css) {
81-
compilation.assets[this.options.css] = data.map ?
82-
new sources.SourceMapSource(
83-
data.css,
84-
this.options.css,
85-
data.map
86-
) :
87-
new sources.RawSource(
88-
data.css
89-
);
90-
91-
// Write out external source map if it exists
92-
if(data.map) {
93-
compilation.assets[`${this.options.css}.map`] = new sources.RawSource(
94-
data.map.toString()
95-
);
96-
}
97-
}
98-
99-
if(this.options.json) {
100-
compilation.assets[this.options.json] = new sources.RawSource(
101-
JSON.stringify(data.compositions, null, 4)
102-
);
103-
}
27+
// Runs before compilation begins
28+
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
29+
// Make processor instance available to the loader
30+
compilation.options.processor = this.processor;
31+
32+
// TODO: Figure out how to tell what files might have changed
33+
// This code is only useful when calling .run() multiple times
34+
// watching handles its own invalidations
35+
// if(compiler.modifiedFiles || compiler.removedFiles) {
36+
// compiler.modifiedFiles.forEach((file) => this.processor.invalidate(file));
37+
// compiler.removedFiles.forEach((file) => this.processor.remove(file));
38+
// }
39+
});
10440

105-
return done();
106-
});
107-
};
41+
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
42+
compilation.hooks.processAssets.tapPromise({
43+
name : PLUGIN_NAME,
44+
stage : compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
45+
}, async (assets) => {
46+
// Don't even bother if errors happened
47+
if(compilation.errors.length) {
48+
return;
49+
}
50+
51+
const data = await this.processor.output({
52+
to : this.options.css || false,
53+
});
54+
55+
if(this.options.css) {
56+
assets[this.options.css] = data.map ?
57+
new sources.SourceMapSource(
58+
data.css,
59+
this.options.css,
60+
data.map
61+
) :
62+
new sources.RawSource(
63+
data.css
64+
);
65+
66+
// Write out external source map if it exists
67+
if(data.map) {
68+
assets[`${this.options.css}.map`] = new sources.RawSource(
69+
data.map.toString()
70+
);
71+
}
72+
}
73+
74+
if(this.options.json) {
75+
assets[this.options.json] = new sources.RawSource(
76+
JSON.stringify(data.compositions, null, 4)
77+
);
78+
}
79+
});
80+
});
81+
}
82+
}
10883

10984
module.exports = ModularCSS;

0 commit comments

Comments
 (0)