Skip to content

Commit df866df

Browse files
committed
inital work
1 parent c39d9ea commit df866df

11 files changed

+746
-51
lines changed

.babelrc

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
{
66
"useBuiltIns": true,
77
"targets": {
8-
"node": "4.3"
8+
"node": "6.11.5"
99
},
1010
"exclude": [
1111
"transform-async-to-generator",
@@ -32,4 +32,4 @@
3232
]
3333
}
3434
}
35-
}
35+
}

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ npm-debug.log*
44
.eslintcache
55
/coverage
66
/dist
7+
/js
78
/local
89
/reports
910
/node_modules
@@ -12,4 +13,4 @@ Thumbs.db
1213
.idea
1314
.vscode
1415
*.sublime-project
15-
*.sublime-workspace
16+
*.sublime-workspace

jest.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
transformIgnorePatterns: [
3+
'/node_modules/',
4+
'<rootDir>/dist/',
5+
],
6+
};

package.json

+7-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"babel-polyfill": "^6.26.0",
3434
"babel-preset-env": "^1.6.1",
3535
"cross-env": "^5.1.3",
36+
"css-loader": "^0.28.10",
3637
"del-cli": "^1.1.0",
3738
"eslint": "^4.17.0",
3839
"eslint-config-webpack": "^1.2.5",
@@ -49,16 +50,20 @@
4950
"dist"
5051
],
5152
"engines": {
52-
"node": ">= 4.3 < 5.0.0 || >= 5.10"
53+
"node": ">= 6.11.5"
5354
},
5455
"peerDependencies": {
55-
"webpack": "^2.0.0 || ^3.0.0"
56+
"webpack": "^4.0.1"
5657
},
5758
"pre-commit": "lint-staged",
5859
"lint-staged": {
5960
"*.js": [
6061
"eslint --fix",
6162
"git add"
6263
]
64+
},
65+
"dependencies": {
66+
"loader-utils": "^1.1.0",
67+
"webpack-sources": "^1.1.0"
6368
}
6469
}

src/index.js

+61-21
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import fs from 'fs';
22
import path from 'path';
33
import webpack from 'webpack';
4+
import sources from 'webpack-sources';
5+
6+
const { RawSource, ConcatSource } = sources;
47

58
const NS = path.dirname(fs.realpathSync(__filename));
69

7-
class CssDependency {
8-
constructor({ identifier, content, media, sourceMap }) {
10+
class CssDependency extends webpack.Dependency {
11+
constructor({ identifier, content, media, sourceMap }, context) {
12+
super();
913
this.identifier = identifier;
1014
this.content = content;
1115
this.media = media;
1216
this.sourceMap = sourceMap;
17+
this.context = context;
1318
}
1419

1520
getResourceIdentifier() {
@@ -23,16 +28,35 @@ class CssDependencyTemplate {
2328

2429
class CssModule extends webpack.Module {
2530
constructor(dependency) {
26-
super(NS);
31+
super(NS, dependency.context);
2732
this._identifier = dependency.identifier;
2833
this.content = dependency.content;
2934
this.media = dependency.media;
3035
this.sourceMap = dependency.sourceMap;
3136
}
3237

38+
source() {
39+
// TODO add webpack support for omitting js source
40+
return new RawSource('// extracted by mini-css-extract-plugin');
41+
}
42+
43+
size() {
44+
return 0;
45+
}
46+
3347
identifier() {
3448
return `css-module ${this._identifier}`;
3549
}
50+
51+
readableIdentifier(requestShortener) {
52+
return `css-modules ${requestShortener.shorten(this._identifier)}`;
53+
}
54+
55+
build(options, compilation, resolver, fs, callback) {
56+
this.buildInfo = {};
57+
this.buildMeta = {};
58+
callback();
59+
}
3660
}
3761

3862
class CssModuleFactory {
@@ -42,6 +66,16 @@ class CssModuleFactory {
4266
}
4367

4468
class MiniCssExtractPlugin {
69+
constructor(options) {
70+
this.options = Object.assign({
71+
filename: '[name].css',
72+
}, options);
73+
if (!this.options.chunkFilename) {
74+
// TODO use webpack conversion style here
75+
this.options.chunkFilename = this.options.filename;
76+
}
77+
}
78+
4579
apply(compiler) {
4680
compiler.hooks.thisCompilation.tap('mini-css-extract-plugin', (compilation) => {
4781
compilation.hooks.normalModuleLoader.tap('mini-css-extract-plugin', (lc, m) => {
@@ -52,38 +86,44 @@ class MiniCssExtractPlugin {
5286
throw new Error(`Exported value was not extracted as an array: ${JSON.stringify(content)}`);
5387
}
5488
for (const line of content) {
55-
module.addDependency(new CssDependency(line));
89+
module.addDependency(new CssDependency(line, m.context));
5690
}
5791
};
5892
});
5993
compilation.dependencyFactories.set(CssDependency, new CssModuleFactory());
6094
compilation.dependencyTemplates.set(CssDependency, new CssDependencyTemplate());
61-
compilation.mainTemplate.hooks.shouldRenderModuleForJavascript.tap('mini-css-extract-plugin', (module) => {
62-
if (module.type === NS) return false;
63-
return undefined;
64-
});
65-
compilation.chunkTemplate.hooks.shouldRenderModuleForJavascript.tap('mini-css-extract-plugin', (module) => {
66-
if (module.type === NS) return false;
67-
return undefined;
68-
});
69-
compilation.mainTemplate.hooks.renderManifest.tap('mini-css-extract-plugin', (result, options) => {
70-
const chunk = options.chunk;
71-
const renderedContent = Array.from(chunk.modulesIterable, m => module[NS]).filter(Boolean);
72-
if (renderedContent.length > 0) {
95+
compilation.mainTemplate.hooks.renderManifest.tap('mini-css-extract-plugin', (result, { chunk }) => {
96+
const renderedModules = Array.from(chunk.modulesIterable).filter(module => module.type === NS);
97+
if (renderedModules.length > 0) {
7398
result.push({
74-
render: () => this.renderContentAsset(renderedContent),
75-
filenameTemplate: filename,
99+
render: () => this.renderContentAsset(renderedModules),
100+
filenameTemplate: this.options.filename,
76101
pathOptions: {
77-
chunk
102+
chunk,
78103
},
79-
identifier: `extract-text-webpack-plugin.${id}.${chunk.id}`
104+
identifier: `extract-text-webpack-plugin.${chunk.id}`,
80105
});
81106
}
82107
});
83108
});
84109
}
110+
renderContentAsset(modules) {
111+
modules.sort((a, b) => a.index2 - b.index2);
112+
// TODO put @import on top
113+
const source = new ConcatSource();
114+
for (const m of modules) {
115+
source.add(m.content);
116+
source.add('\n');
117+
if (m.media) {
118+
source.prepend(`@media ${m.media} {\n`);
119+
source.add('}\n');
120+
}
121+
return source;
122+
}
123+
return source;
124+
}
85125
}
86126

87127
MiniCssExtractPlugin.loader = require.resolve('./loader');
88128

89-
module.exports = MiniCssExtractPlugin;
129+
export default MiniCssExtractPlugin;

src/loader.js

+12-12
Original file line numberDiff line numberDiff line change
@@ -10,42 +10,41 @@ import LimitChunkCountPlugin from 'webpack/lib/optimize/LimitChunkCountPlugin';
1010

1111
const NS = path.dirname(fs.realpathSync(__filename));
1212

13-
const exec = (code, filename) => {
14-
const module = new NativeModule(filename, this);
15-
module.paths = NativeModule._nodeModulePaths(this.context); // eslint-disable-line no-underscore-dangle
13+
const exec = (loaderContext, code, filename) => {
14+
const module = new NativeModule(filename, loaderContext);
15+
module.paths = NativeModule._nodeModulePaths(loaderContext.context); // eslint-disable-line no-underscore-dangle
1616
module.filename = filename;
1717
module._compile(code, filename); // eslint-disable-line no-underscore-dangle
1818
return module.exports;
1919
};
2020

2121
const findModuleById = (modules, id) => {
22-
for(const module of modules) {
23-
if(module.id === id)
24-
return module;
22+
for (const module of modules) {
23+
if (module.id === id) { return module; }
2524
}
2625
return null;
27-
}
26+
};
2827

2928
export function pitch(request) {
3029
const query = loaderUtils.getOptions(this) || {};
3130
const loaders = this.loaders.slice(this.loaderIndex + 1);
3231
this.addDependency(this.resourcePath);
33-
const childFilename = 'extract-text-webpack-plugin-output-filename'; // eslint-disable-line no-path-concat
32+
const childFilename = 'mini-css-extract-plugin-output-filename'; // eslint-disable-line no-path-concat
3433
const publicPath = typeof query.publicPath === 'string' ? query.publicPath : this._compilation.outputOptions.publicPath;
3534
const outputOptions = {
3635
filename: childFilename,
3736
publicPath,
3837
};
39-
const childCompiler = this._compilation.createChildCompiler(`extract-text-webpack-plugin ${NS} ${request}`, outputOptions);
38+
const childCompiler = this._compilation.createChildCompiler(`mini-css-extract-plugin ${NS} ${request}`, outputOptions);
4039
childCompiler.apply(new NodeTemplatePlugin(outputOptions));
4140
childCompiler.apply(new LibraryTemplatePlugin(null, 'commonjs2'));
4241
childCompiler.apply(new NodeTargetPlugin());
4342
childCompiler.apply(new SingleEntryPlugin(this.context, `!!${request}`));
4443
childCompiler.apply(new LimitChunkCountPlugin({ maxChunks: 1 }));
4544
// We set loaderContext[NS] = false to indicate we already in
4645
// a child compiler so we don't spawn another child compilers from there.
47-
childCompiler.hooks.thisCompilation.tap((compilation) => {
48-
compilation.hooks.normalModuleLoader.tap((loaderContext, module) => {
46+
childCompiler.hooks.thisCompilation.tap('mini-css-extract-plugin loader', (compilation) => {
47+
compilation.hooks.normalModuleLoader.tap('mini-css-extract-plugin loader', (loaderContext, module) => {
4948
loaderContext[NS] = false; // eslint-disable-line no-param-reassign
5049
if (module.request === request) {
5150
module.loaders = loaders.map((loader) => { // eslint-disable-line no-param-reassign
@@ -90,7 +89,7 @@ export function pitch(request) {
9089
}
9190
let text;
9291
try {
93-
text = exec(source, request);
92+
text = exec(this, source, request);
9493
if (!Array.isArray(text)) {
9594
text = [[null, text]];
9695
} else {
@@ -112,6 +111,7 @@ export function pitch(request) {
112111
if (text.locals && typeof resultSource !== 'undefined') {
113112
resultSource = `module.exports = ${JSON.stringify(text.locals)};`;
114113
}
114+
115115
return callback(null, resultSource);
116116
});
117117
}

test/TestCases.test.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import webpack from 'webpack';
4+
5+
describe('TestCases', () => {
6+
const casesDirectory = path.resolve(__dirname, 'cases');
7+
const outputDirectory = path.resolve(__dirname, '../js');
8+
for (const directory of fs.readdirSync(casesDirectory)) {
9+
if (!/^(\.|_)/.test(directory)) {
10+
// eslint-disable-next-line no-loop-func
11+
it(`${directory} should compile to the expected result`, (done) => {
12+
const directoryForCase = path.resolve(casesDirectory, directory);
13+
const outputDirectoryForCase = path.resolve(outputDirectory, directory);
14+
// eslint-disable-next-line import/no-dynamic-require, global-require
15+
const webpackConfig = require(path.resolve(directoryForCase, 'webpack.config.js'));
16+
webpack(Object.assign({
17+
mode: 'none',
18+
context: directoryForCase,
19+
output: {
20+
path: outputDirectoryForCase,
21+
},
22+
}, webpackConfig), (err, stats) => {
23+
if (err) {
24+
done(err);
25+
return;
26+
}
27+
done();
28+
// eslint-disable-next-line no-console
29+
console.log(stats.toString());
30+
if (stats.hasErrors()) {
31+
done(new Error(stats.toString()));
32+
return;
33+
}
34+
const expectedDirectory = path.resolve(directoryForCase, 'expected');
35+
for (const file of fs.readdirSync(expectedDirectory)) {
36+
const content = fs.readFileSync(path.resolve(expectedDirectory, file), 'utf-8');
37+
const actualContent = fs.readFileSync(path.resolve(outputDirectoryForCase, file), 'utf-8');
38+
expect(actualContent).toBeEqual(content);
39+
}
40+
done();
41+
});
42+
}, 10000);
43+
}
44+
}
45+
});

test/cases/simple/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './style.css';

test/cases/simple/style.css

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
body { background: red; }

test/cases/simple/webpack.config.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const Self = require('../../../');
2+
3+
module.exports = {
4+
entry: './index.js',
5+
module: {
6+
rules: [
7+
{
8+
test: /\.css$/,
9+
use: [
10+
Self.loader,
11+
'css-loader',
12+
],
13+
},
14+
],
15+
},
16+
plugins: [
17+
new Self({
18+
filename: '[name].css',
19+
}),
20+
],
21+
};

0 commit comments

Comments
 (0)