├── mini-pack
├── readme.md
├── cli.js
├── webpack-sources
│ ├── RawSource.js
│ ├── OriginalSource.js
│ ├── CachedSource.js
│ ├── PrefixSource.js
│ ├── ConcatSource.js
│ └── ReplaceSource.js
├── lib
│ ├── node
│ │ └── NodeEnvironmentPlugin.js
│ ├── dependencies
│ │ ├── DependencyReference.js
│ │ ├── CommonJsPlugin.js
│ │ ├── CommonJsRequireDependency.js
│ │ └── ModuleDependencyTemplateAsId.js
│ ├── Stats.js
│ ├── FunctionModulePlugin.js
│ ├── web
│ │ ├── JsonpChunkTemplatePlugin.js
│ │ ├── JsonpMainTemplatePlugin.js
│ │ └── JsonpTemplatePlugin.js
│ ├── ChunkTemplate.js
│ ├── NamedChunksPlugin.js
│ ├── TemplatedPathPlugin.js
│ ├── EntryOptionPlugin.js
│ ├── NamedModulesPlugin.js
│ ├── util
│ │ └── createHash.js
│ ├── Chunk.js
│ ├── webpack.js
│ ├── JavascriptGenerator.js
│ ├── SingleEntryPlugin.js
│ ├── ResolverFactory.js
│ ├── Parser.js
│ ├── Entrypoint.js
│ ├── FunctionModuleTemplatePlugin.js
│ ├── ModuleTemplate.js
│ ├── Template.js
│ ├── JavascriptModulesPlugin.js
│ ├── RuleSet.js
│ ├── WebpackOptionsApply.js
│ ├── Compiler.js
│ ├── NormalModuleFactory.js
│ ├── NormalModule.js
│ ├── buildChunkGraph.js
│ ├── MainTemplate.js
│ ├── Compilation.js
│ └── WebpackOptionsDefaulter.js
├── enhanced-resolve
│ ├── DescriptionFileUtils.js
│ ├── ResultPlugin.js
│ ├── AliasFieldPlugin.js
│ ├── ParsePlugin.js
│ ├── JoinRequestPlugin.js
│ ├── UnsafeCachePlugin.js
│ ├── ResolverFactory.js
│ ├── DescriptionFilePlugin.js
│ └── Resolver.js
└── loader-runner
│ └── LoaderRunner.js
├── src
├── default.css
├── default.less
├── a.js
├── 2.jpg
├── 2.png
├── index.js
├── common.less
├── test.js
├── index.less
├── index.css
├── common.css
└── temp.js
├── debugger.js
├── webpack-main.png
├── loaders
├── css-loader
│ ├── runtime
│ │ ├── getUrl.js
│ │ └── api.js
│ ├── utils.js
│ ├── plugins
│ │ ├── postcss-import-parser.js
│ │ └── postcss-url-parser.js
│ ├── index.js
│ └── readme.md
├── loader2.js
├── file-loader
│ └── index.js
├── url-loader
│ └── index.js
├── babel-loader
│ └── index.js
├── less-loader
│ └── index.js
├── loader1.js
├── style-loader
│ ├── runtime
│ │ └── injectStylesIntoStyleTag.js
│ └── index.js
└── loader指南.md
├── .gitignore
├── plugins
├── MiniCssExtractPlugin
│ └── loader.js
├── plugin1.js
└── MediaExtractPlugin
│ ├── postcss-plugins
│ └── postcss-media-parser.js
│ ├── index.js
│ └── readme.md
├── index.html
├── package.json
├── webpack.config.js
└── README.md
/mini-pack/readme.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/default.css:
--------------------------------------------------------------------------------
1 | span{
2 | color: green;
3 | }
--------------------------------------------------------------------------------
/src/default.less:
--------------------------------------------------------------------------------
1 | span{
2 | color: blue;
3 | }
--------------------------------------------------------------------------------
/src/a.js:
--------------------------------------------------------------------------------
1 | console.log('a.js')
2 | module.exports = 'a.js file'
--------------------------------------------------------------------------------
/debugger.js:
--------------------------------------------------------------------------------
1 | debugger;
2 | require('./node_modules/webpack/bin/webpack')
3 |
--------------------------------------------------------------------------------
/src/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizuncong/mini-webpack/HEAD/src/2.jpg
--------------------------------------------------------------------------------
/src/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizuncong/mini-webpack/HEAD/src/2.png
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const test = require('./test.js')
2 |
3 | console.log(test)
4 |
--------------------------------------------------------------------------------
/webpack-main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizuncong/mini-webpack/HEAD/webpack-main.png
--------------------------------------------------------------------------------
/src/common.less:
--------------------------------------------------------------------------------
1 | img {
2 | height: 400px;
3 | overflow: hidden;
4 | }
5 | @import './default.less';
--------------------------------------------------------------------------------
/loaders/css-loader/runtime/getUrl.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = function (url) {
4 | return url;
5 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
3 | node_modules
4 |
5 | dist
6 | dll
7 |
8 | .npm
9 | stats.json
10 |
11 | .DS_Store
12 |
--------------------------------------------------------------------------------
/src/test.js:
--------------------------------------------------------------------------------
1 | const a = require('./a.js')
2 |
3 | console.log('a==', a)
4 | console.log('test.js')
5 | module.exports = 'test.js file'
--------------------------------------------------------------------------------
/src/index.less:
--------------------------------------------------------------------------------
1 | @color: red;
2 | @import './common.less';
3 | body{
4 | background: @color;
5 | background: url('./2.jpg');
6 | }
--------------------------------------------------------------------------------
/plugins/MiniCssExtractPlugin/loader.js:
--------------------------------------------------------------------------------
1 | function loader(){
2 |
3 | }
4 |
5 | loader.pitch = function(request){
6 |
7 | }
8 |
9 | module.exports = loader
--------------------------------------------------------------------------------
/mini-pack/cli.js:
--------------------------------------------------------------------------------
1 | const webpack = require('./lib/webpack.js')
2 | const options = require('../webpack.config')
3 |
4 | const compiler = webpack(options)
5 | compiler.run((err, stat) => {
6 | stat.toJson();
7 | })
--------------------------------------------------------------------------------
/mini-pack/webpack-sources/RawSource.js:
--------------------------------------------------------------------------------
1 |
2 | class RawSource {
3 | constructor(value) {
4 | this._value = value;
5 | }
6 |
7 | source() {
8 | return this._value;
9 | }
10 | }
11 |
12 | module.exports = RawSource;
13 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | container
127 |
我是红色
128 |
129 |
130 | ```
131 |
132 |
133 | ### 优点
134 | 在重度响应式设计系统中,能够减少css文件大小,并且根据多媒体类型应用有效的css样式,优先加载有效的css资源,提高css下载效率,解析之行效率,减少首屏时间
135 |
136 | ### todo
137 | - 目前场景比较单一,只针对了 @media (max-width: ...px) 做处理。实际项目中需要设置几个断点,比如
138 | + 移动端: max-width: 760px
139 | + ipad: max-width: 960px
140 | + 大于960px的视为pc端
141 |
142 | - 需要支持sourcemap能力
143 | - 需要支持代码分割懒加载能力。
--------------------------------------------------------------------------------
/loaders/css-loader/plugins/postcss-url-parser.js:
--------------------------------------------------------------------------------
1 | const needParseDeclaration = /(?:url|(?:-webkit-)?image-set)\(/i;
2 |
3 | const plugin = (options = {}) => {
4 | return {
5 | postcssPlugin: "postcss-url-parser",
6 |
7 | prepare(result) {
8 | const parsedDeclarations = [];
9 | return {
10 | Declaration(declaration) {
11 | // 解析值为 url(./2.png)的声明
12 | if (!needParseDeclaration.test(declaration.value)) {
13 | return;
14 | }
15 | const reg = /url\((.+?)\)/g;
16 | const res = reg.exec(declaration.value)
17 | parsedDeclarations.push({
18 | declaration,
19 | url: res[1].replace(/['""]/g, "") // './2.png'
20 | })
21 | },
22 |
23 | async OnceExit() {
24 | if (parsedDeclarations.length === 0) {
25 | return;
26 | }
27 |
28 | const resolvedDeclarations = await Promise.all(parsedDeclarations.map(async parsedDeclaration => {
29 | const {
30 | url
31 | } = parsedDeclaration;
32 |
33 | const {
34 | resolver,
35 | context
36 | } = options;
37 | const resolvedUrl = await resolver(context, url);
38 |
39 | if (!resolvedUrl) {
40 | return;
41 | }
42 |
43 |
44 | return {
45 | ...parsedDeclaration,
46 | url: resolvedUrl, // '/Users/lizuncong/Documents/手写源码系列/mini-webpack/src/2.png'
47 | };
48 | }));
49 |
50 |
51 |
52 | const urlToNameMap = new Map();
53 | const urlToReplacementMap = new Map();
54 | let hasUrlImportHelper = false;
55 |
56 | for (let index = 0; index <= resolvedDeclarations.length - 1; index++) {
57 | const item = resolvedDeclarations[index];
58 | if (!hasUrlImportHelper) {
59 | options.imports.push({
60 | importName: "___CSS_LOADER_GET_URL_IMPORT___",
61 | url: options.urlHandler(require.resolve("../runtime/getUrl.js")),
62 | index: -1
63 | });
64 | hasUrlImportHelper = true;
65 | }
66 |
67 | const { url } = item; // url: '/Users/lizuncong/Documents/手写源码系列/mini-webpack/src/2.png'
68 | const newUrl = url;
69 | let importName = urlToNameMap.get(newUrl);
70 |
71 | if (!importName) {
72 | importName = `___CSS_LOADER_URL_IMPORT_${urlToNameMap.size}___`;
73 | urlToNameMap.set(newUrl, importName);
74 | options.imports.push({
75 | importName, // '___CSS_LOADER_URL_IMPORT_0___'
76 | url: options.urlHandler(newUrl), // '"./2.png"'
77 | index
78 | });
79 | }
80 |
81 | const replacementKey = JSON.stringify({
82 | newUrl
83 | });
84 | let replacementName = urlToReplacementMap.get(replacementKey);
85 |
86 | if (!replacementName) {
87 | replacementName = `___CSS_LOADER_URL_REPLACEMENT_${urlToReplacementMap.size}___`;
88 | urlToReplacementMap.set(replacementKey, replacementName);
89 | options.replacements.push({
90 | replacementName, // '___CSS_LOADER_URL_REPLACEMENT_0___'
91 | importName, // '___CSS_LOADER_URL_IMPORT_0___'
92 | });
93 | }
94 |
95 | item.declaration.value = `url(${replacementName})`;
96 | }
97 | }
98 |
99 | };
100 | }
101 |
102 | };
103 | };
104 |
105 | plugin.postcss = true;
106 |
107 | module.exports = plugin;
108 |
--------------------------------------------------------------------------------
/loaders/css-loader/index.js:
--------------------------------------------------------------------------------
1 | const postcss = require('postcss')
2 | const loaderUtils = require('loader-utils')
3 | const postcssImportParser = require('./plugins/postcss-import-parser')
4 | const postcssUrlParser = require('./plugins/postcss-url-parser')
5 | const { getImportCode, getModuleCode, getExportCode } = require('./utils')
6 |
7 |
8 | async function loader(source){
9 | const callback = this.async();
10 | const options = {
11 | import:true,
12 | esModule:true,
13 | importLoaders:undefined,
14 | modules:false,
15 | sourceMap:false,
16 | url:true
17 | }
18 |
19 | // 处理 @import './common.css'
20 | const importPluginImports = [];
21 | const importPluginApi = [];
22 | const resolver = this.getResolve({});
23 | // 处理url(./2.png)
24 | const urlPluginImports = [];
25 | const replacements = [];
26 |
27 | const {
28 | resourcePath
29 | } = this;
30 | const result = await postcss([
31 | postcssImportParser({
32 | imports: importPluginImports,
33 | api: importPluginApi,
34 | resolver,
35 | context: this.context,
36 | // urlHandler: url => loaderUtils.stringifyRequest(this, url)
37 | urlHandler: url => {
38 | const loadersRequest = this.loaders.slice(this.loaderIndex, this.loaderIndex + 1).map(x => x.request).join("!");
39 | const req = `-!${loadersRequest}!`
40 | const comReq = req + url
41 | return loaderUtils.stringifyRequest(this, comReq)
42 | }
43 | }),
44 | postcssUrlParser({
45 | imports: urlPluginImports,
46 | replacements,
47 | context: this.context,
48 | resolver,
49 | urlHandler: url => loaderUtils.stringifyRequest(this, url)
50 | })
51 | ])
52 | .process(source, {
53 | hideNothingWarning: true,
54 | from: resourcePath,
55 | to: resourcePath,
56 | map: false
57 | })
58 | // importPluginImports是个数组,数组里的元素为:
59 | // importName:'___CSS_LOADER_AT_RULE_IMPORT_0___'
60 | // index:0
61 | // url:'"-!../node_modules/css-loader/dist/cjs.js??ref--7-0!./common.css"'
62 | const imports = [].concat(importPluginImports).concat(urlPluginImports);
63 | // importPluginApi是个数组,数组里的元素为:
64 | // importName:'___CSS_LOADER_AT_RULE_IMPORT_0___'
65 | // index:0
66 | const api = [].concat(importPluginApi);
67 | imports.unshift({
68 | importName: "___CSS_LOADER_API_IMPORT___",
69 | url: loaderUtils.stringifyRequest(this, require.resolve("./runtime/api"))
70 | });
71 |
72 | // getImportCode 输出:
73 | // Imports
74 | // import ___CSS_LOADER_API_IMPORT___ from "../node_modules/css-loader/dist/runtime/api.js";
75 | // import ___CSS_LOADER_AT_RULE_IMPORT_0___ from "-!../node_modules/css-loader/dist/cjs.js??ref--7-0!./common.css";
76 | const importCode = getImportCode(imports)
77 | // console.log('==============importCode==============')
78 | // console.log(importCode)
79 | // getModuleCode输出:
80 | // var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]});
81 | // ___CSS_LOADER_EXPORT___.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
82 | // // Module
83 | // ___CSS_LOADER_EXPORT___.push([module.id, "body{\n color: blue;\n background: yellow;\n}\n\n\n.container{\n color: red;\n}\n\n", ""]);
84 | const moduleCode = getModuleCode(result, api, replacements)
85 | // console.log('==============moduleCode==============')
86 | // console.log(moduleCode)
87 | // getExportCode输出:
88 | // Exports
89 | // export default ___CSS_LOADER_EXPORT___;
90 | const exportCode = getExportCode(options)
91 | // console.log('==============exportCode==============')
92 | // console.log(exportCode)
93 |
94 | const str = `${importCode}${moduleCode}${exportCode}`
95 | console.log('===========my css loader==========')
96 | console.log(str)
97 | console.log('===========my css loader 2222==========')
98 | callback(null, str);
99 |
100 | }
101 |
102 | module.exports = loader
103 |
104 |
--------------------------------------------------------------------------------
/mini-pack/lib/Compiler.js:
--------------------------------------------------------------------------------
1 | const {
2 | Tapable,
3 | SyncHook,
4 | SyncBailHook,
5 | AsyncParallelHook,
6 | AsyncSeriesHook
7 | } = require("tapable");
8 | const ResolverFactory = require("./ResolverFactory");
9 | const NormalModuleFactory = require('./NormalModuleFactory')
10 | const Compilation = require('./Compilation')
11 | const Stats = require('./Stats')
12 | const path = require('path')
13 | class Compiler extends Tapable {
14 | constructor(context){
15 | super();
16 | // 这些钩子大部分在WebpackOptionsApply实例中注册插件
17 | this.hooks = {
18 | afterCompile: new AsyncSeriesHook(["compilation"]),
19 | afterResolvers: new SyncHook(["compiler"]),
20 | beforeRun: new AsyncSeriesHook(["compiler"]),
21 | run: new AsyncSeriesHook(["compiler"]),
22 | watchRun: new AsyncSeriesHook(["compiler"]),
23 | thisCompilation: new SyncHook(["compilation", "params"]),
24 | entryOption: new SyncBailHook(["context", "entry"]),
25 | make: new AsyncParallelHook(["compilation"]),
26 | compilation: new SyncHook(["compilation", "params"]),
27 | emit: new AsyncSeriesHook(["compilation"]),
28 | }
29 | this.name = undefined;
30 |
31 | // 用于创建resolver实例
32 | this.resolverFactory = new ResolverFactory();
33 | this.outputPath = "";
34 | this.outputFileSystem = null;
35 | this.inputFileSystem = null;
36 | this.options = {} // webpack options
37 | this.context = context
38 | }
39 | emitAssets(compilation, callback){
40 | let outputPath;
41 | const emitFiles = err => {
42 | const assets = compilation.getAssets()
43 | for(const { name: targetFile, source } of assets){
44 | const targetPath = path.posix.join(outputPath, targetFile)
45 | let content = source.source();
46 | this.outputFileSystem.writeFileSync(targetPath, content)
47 | }
48 | callback()
49 | }
50 | this.hooks.emit.callAsync(compilation, (err) => {
51 | // 在简单的主流程中,其实outputPath可以直接赋值this.outputPath
52 | outputPath = compilation.getPath(this.outputPath);
53 | this.outputFileSystem.mkdir(outputPath, emitFiles);
54 | })
55 | }
56 |
57 | run(finalCallback){
58 | const onCompiled = (err, compilation) => { // 编译完成后的回调
59 | this.emitAssets(compilation, (err) => {
60 | const stats = new Stats(compilation)
61 | finalCallback(null, stats)
62 | })
63 | }
64 | this.compile(onCompiled)
65 | }
66 |
67 | newCompilation(params){
68 | const compilation = new Compilation(this)
69 | this.hooks.thisCompilation.call(compilation, params)
70 | // 主要是为compilation.dependencyFactories设置值以及
71 | // normalModuleFactory.hooks注册钩子
72 | // 触发SingleEntryPlugin中注册的插件执行,从而为compilation.dependencyFactories
73 | // 设置对应的params.normalModuleFactory值
74 | this.hooks.compilation.call(compilation, params)
75 | return compilation
76 | }
77 |
78 |
79 | compile(onCompiled){
80 | const params = {
81 | // normalModuleFactory负责调用compiler.resolverFactory.create方法创建
82 | // enhanced-resolver实例。normalModuleFactory也是负责解析模块的工厂实例
83 | normalModuleFactory: new NormalModuleFactory(
84 | this.options.context,
85 | this.resolverFactory,
86 | this.options.module || {}
87 | ),
88 | }
89 | const compilation = this.newCompilation(params)
90 | // 创建完compilation后,触发compilation的addEntry方法。
91 | // SingleEntryPlugin 监听了make事件,触发compilation.addEntry方法
92 | this.hooks.make.callAsync(compilation, err => {
93 | compilation.finish(err => {
94 | compilation.seal(err => { // 通过模块生成代码块
95 | onCompiled(err, compilation)
96 | })
97 | })
98 | })
99 | }
100 | }
101 |
102 | module.exports = Compiler
--------------------------------------------------------------------------------
/mini-pack/lib/NormalModuleFactory.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const path = require("path");
4 | const {
5 | Tapable,
6 | SyncBailHook
7 | } = require("tapable");
8 | const RuleSet = require("./RuleSet");
9 | const NormalModule = require('./NormalModule')
10 |
11 | // 负责以下事情
12 | // 1. 解析模块
13 | // 2. 为模块匹配wepback loaders并执行
14 | class NormalModuleFactory extends Tapable {
15 | constructor(context, resolverFactory, options) {
16 | super();
17 | this.hooks = {
18 | createParser: new SyncBailHook(["parserOptions"]),
19 | createGenerator: new SyncBailHook(["generatorOptions"])
20 | }
21 | this.context = context || "";
22 | // resolverFactory负责创建对应的moduleFactory实例
23 | this.resolverFactory = resolverFactory;
24 | this.ruleSet = new RuleSet(options.defaultRules.concat(options.rules));
25 | }
26 | create(data, callback){
27 | const dependencies = data.dependencies;
28 | const context = data.context || this.context;
29 | const resolveOptions = data.resolveOptions || {};
30 | const request = dependencies[0].request;
31 | const contextInfo = data.contextInfo || {};
32 | // 调用this.resolverFactory创建对应的resolver实例
33 | const normalResolver = this.getResolver("normal", data.resolveOptions);
34 | // normalResolver.resolve仅仅用于解析模块,获取文件路径等信息,而不是读取源码
35 | normalResolver.resolve(
36 | contextInfo,
37 | context,
38 | request,
39 | {},
40 | (err, resource, resourceResolveData) => {
41 | // resource, 文件的绝对路径,比如'/Users/lizc/Documents/MYProjects/mini-webpack/src/index.js'
42 | // resourceResolveData,包含文件的路径信息,以及package.json内容
43 |
44 | // 为模块 './src/index.js' 匹配对应的webpack loader
45 | const result = this.ruleSet.exec({
46 | resource,
47 | realResource: resource,
48 | resourceQuery: "",
49 | issuer: contextInfo.issuer,
50 | compiler: contextInfo.compiler
51 | })
52 | const useLoaders = [];
53 | for(const r of result){
54 | if(r.type === 'use'){
55 | useLoaders.push(r.value)
56 | }
57 | }
58 | const parser = this.createParser();
59 | const generator = this.createGenerator();
60 | const data = {
61 | context: context,
62 | dependencies: dependencies,
63 | generator: generator,
64 | loaders: useLoaders, // webpack loaders数组
65 | parser: parser,
66 | rawRequest: request, // './src/index.js'
67 | request: useLoaders // './src/index.js'
68 | .map(l => l.loader)
69 | .concat([resource])
70 | .join("!"),
71 | resolveOptions: {},
72 | resource, // '/Users/lizc/Documents/MYProjects/mini-webpack/src/index.js'
73 | userRequest: resource, // '/Users/lizc/Documents/MYProjects/mini-webpack/src/index.js'
74 | resourceResolveData,
75 | settings: {
76 | resolve: {},
77 | type: 'javascript/auto'
78 | },
79 | type: 'javascript/auto',
80 | }
81 | // 为每一个文件创建一个对应的NormalModule实例
82 | const createdModule = new NormalModule(data);
83 | callback(null, createdModule);
84 | }
85 | )
86 | }
87 | createParser(parserOptions = {}) {
88 | const parser = this.hooks.createParser.call(parserOptions);
89 | return parser;
90 | }
91 |
92 | createGenerator() {
93 | const generator = this.hooks.createGenerator.call();
94 | return generator;
95 | }
96 |
97 | getResolver(type, resolveOptions) {
98 | return this.resolverFactory.get(
99 | type,
100 | resolveOptions || {}
101 | );
102 | }
103 |
104 | }
105 |
106 | module.exports = NormalModuleFactory;
107 |
--------------------------------------------------------------------------------
/mini-pack/lib/NormalModule.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path')
3 | const { runLoaders } =require('../loader-runner/LoaderRunner')
4 | const OriginalSource = require('../webpack-sources/OriginalSource')
5 | const CachedSource = require('../webpack-sources/CachedSource')
6 | const createHash = require('./util/createHash')
7 | function dirname(path) {
8 | if(path === "/") return "/";
9 | var i = path.lastIndexOf("/");
10 | var j = path.lastIndexOf("\\");
11 | var i2 = path.indexOf("/");
12 | var j2 = path.indexOf("\\");
13 | var idx = i > j ? i : j;
14 | var idx2 = i > j ? i2 : j2;
15 | if(idx < 0) return path;
16 | if(idx === idx2) return path.substr(0, idx + 1);
17 | return path.substr(0, idx);
18 | }
19 |
20 | // 本质上通过require()引入的文件对应一个NormalModule实例
21 | class NormalModule {
22 | constructor({
23 | type,
24 | request,
25 | userRequest,
26 | rawRequest,
27 | loaders,
28 | resource,
29 | matchResource,
30 | parser,
31 | generator,
32 | resolveOptions,
33 | context
34 | }) {
35 | this.context = dirname(resource);
36 |
37 | // Info from Factory
38 | this.request = request;
39 | this.userRequest = userRequest;
40 | this.rawRequest = rawRequest;
41 | this.parser = parser;
42 | this.generator = generator;
43 | this.resource = resource;
44 | this.matchResource = matchResource;
45 | this.loaders = loaders; // 模块的loaders
46 | this.dependencies = []; // 模块的依赖
47 | this._source = null; // OriginalSource的实例,保存的是模块经过loader处理后的源码
48 | this._chunks = new Set();
49 | this.index = null;
50 | this.index2 = null;
51 | this.id = null;
52 | if (resolveOptions !== undefined) this.resolveOptions = resolveOptions;
53 | }
54 | identifier() {
55 | return this.request;
56 | }
57 | libIdent(options) {
58 | return "./" + path.posix.relative(options.context, this.userRequest);
59 | }
60 | addChunk(chunk) {
61 | this._chunks.add(chunk);
62 | return true;
63 | }
64 | // 1.执行loader,获取模块经过loader处理后的源码
65 | // 2.解析源码得到对应的模块依赖
66 | build(options, compilation, resolver, fs, callback){
67 | const loaderContext = {
68 | rootContext: options.context,
69 | _compilation: compilation,
70 | _compiler: compilation.compiler,
71 | fs,
72 | }
73 | runLoaders(
74 | {
75 | resource: this.resource,
76 | loaders: this.loaders,
77 | context: loaderContext,
78 | readResource: fs.readFile.bind(fs)
79 | },
80 | (err, result) => {
81 | const resourceBuffer = result.resourceBuffer;
82 | const source = result.result[0];
83 | this._source = new OriginalSource(source, this.request)
84 | this._sourceSize = null;
85 | this._ast = null;
86 |
87 | const parseResult = this.parser.parse(
88 | this._source.source(),
89 | {
90 | current: this,
91 | module: this,
92 | compilation,
93 | options
94 | }
95 | )
96 | this._initBuildHash(compilation);
97 | callback();
98 | }
99 | )
100 | }
101 | _initBuildHash(compilation) {
102 | const hash = createHash(compilation.outputOptions.hashFunction);
103 | if (this._source) {
104 | hash.update("source");
105 | this._source.updateHash(hash);
106 | }
107 | hash.update("meta");
108 | hash.update(JSON.stringify({}));
109 | this._buildHash = hash.digest("hex");
110 | }
111 | updateHash(hash) {
112 | hash.update(this._buildHash);
113 | hash.update(`${this.id}`);
114 | for (const dep of this.dependencies) dep.updateHash(hash);
115 | }
116 | originalSource() {
117 | return this._source;
118 | }
119 | source(dependencyTemplates, runtimeTemplate, type = "javascript") {
120 |
121 | const source = this.generator.generate(
122 | this,
123 | dependencyTemplates,
124 | runtimeTemplate,
125 | type
126 | );
127 | const cachedSource = new CachedSource(source);
128 | return cachedSource;
129 | }
130 | }
131 |
132 | module.exports = NormalModule;
133 |
--------------------------------------------------------------------------------
/loaders/loader指南.md:
--------------------------------------------------------------------------------
1 | ### loader的引入方式
2 | loader只能是普通函数,不能使用箭头函数,因为loader中,this提供了整个上下文的环境
3 | #### 第一种引入方式:使用resolveLoader.modules告诉webpack在哪里查找loader
4 | ```js
5 | module.exports = {
6 | resolveLoader: {
7 | modules: ['node_modules', path.resolve(__dirname, 'loaders')],
8 | },
9 | module: {
10 | rules: [
11 | {
12 | test: /\.js$/,
13 | use: 'loader1',
14 | },
15 | ]
16 | }
17 | };
18 | ```
19 | #### 第二种引入方式:使用resolveLoader.alias别名方式
20 | ```js
21 | module.exports = {
22 | resolveLoader: {
23 | alias: {
24 | loader1: path.resolve(__dirname, 'loaders', 'loader1.js')
25 | },
26 | },
27 | module: {
28 | rules: [
29 | {
30 | test: /\.js$/,
31 | use: 'loader1',
32 | },
33 | ]
34 | }
35 | };
36 | ```
37 | #### 第三种引入方式:直接使用path.resolve指定loader位置
38 | ```js
39 | module.exports = {
40 | module: {
41 | rules: [
42 | {
43 | test: /\.js$/,
44 | use: path.resolve(__dirname, 'loaders', 'loader1.js'),
45 | },
46 | ]
47 | }
48 | };
49 | ```
50 |
51 | ### loader执行时机
52 | #### 默认情况下,loader从下往上,从右到左依次执行
53 | 从右往左依次执行,先loader1,其次loade2,最后loader3
54 | ```js
55 | module.exports = {
56 | resolveLoader: {
57 | modules: ['node_modules', path.resolve(__dirname, 'loaders')],
58 | },
59 | module: {
60 | rules: [
61 | {
62 | test: /\.js$/,
63 | use: ['loader3', 'loader2', 'loader1'],
64 | },
65 | ]
66 | }
67 | };
68 | ```
69 | 从下往上依次执行,先loader3,其次loader2,最后loader1
70 | ```js
71 | module.exports = {
72 | resolveLoader: {
73 | modules: ['node_modules', path.resolve(__dirname, 'loaders')],
74 | },
75 | module: {
76 | rules: [
77 | {
78 | test: /\.js$/,
79 | use: 'loader1',
80 | },
81 | {
82 | test: /\.js$/,
83 | use: 'loader2',
84 | },
85 | {
86 | test: /\.js$/,
87 | use: 'loader3',
88 | },
89 | ]
90 | }
91 | };
92 | ```
93 | #### 可以通过enforce改变loader的执行顺序
94 | loader将按照如下方式依次执行
95 |
96 | `pre loader`(配置了enforce: pre的loader) -> `normal loader` -> `inline loader` -> `post loader`(配置了enforce: post的loader)。
97 |
98 | `loader1`以及`loader3`没有配置`enforce`属性,因此是`normal loader`。
99 |
100 | 下面loader的执行顺序是:loader2 -> loader3 -> loader1 -> loader4
101 | ```js
102 | module.exports = {
103 | resolveLoader: {
104 | modules: ['node_modules', path.resolve(__dirname, 'loaders')],
105 | },
106 | module: {
107 | rules: [
108 | {
109 | test: /\.js$/,
110 | use: 'loader1',
111 | },
112 | {
113 | test: /\.js$/,
114 | use: 'loader2',
115 | enforce: "pre"
116 | },
117 | {
118 | test: /\.js$/,
119 | use: 'loader3',
120 | },
121 | {
122 | test: /\.js$/,
123 | use: 'loader4',
124 | enforce: "post"
125 | },
126 | ]
127 | }
128 | };
129 | ```
130 |
131 | #### [inline loader执行顺序](https://webpack.js.org/concepts/loaders/#inline)
132 | 通过`import Styles from '!style-loader!css-loader?modules!./styles.css';`方式引入的就是`inline-loader`
133 |
134 | `inline loader`的使用方式不同,也会改变loader的顺序:
135 |
136 | - 如果`inline loader`前面只有`!`号,则文件不会再通过配置的`normal loader`解析
137 | ```javascript
138 | import Styles from '!style-loader!css-loader?modules!./styles.css';
139 | ```
140 | - 如果`inline loader`前面有`!!`号,则表示文件不再通过其他loader处理,只经过inline loader处理。
141 | ```javascript
142 | import Styles from '!!style-loader!css-loader?modules!./styles.css';
143 | ```
144 | - 如果`inline-loader`前面有`-!`,则不会让文件再去通过`pre loader` 以及 `normal loader`解析,但还是会经过`post loader`解析
145 | ```javascript
146 | import Styles from '-!style-loader!css-loader?modules!./styles.css';
147 | ```
148 |
149 | #### [loader的组成](https://webpack.js.org/api/loaders/#pitching-loader)
150 | loader包含两部分,pitchLoader和normalLoader,pitch和normal的执行顺序正好相反
151 | - 当pitch没有定义或者没有返回值时,会先依次执行pitch在获取资源执行loader
152 | - 如果定义的某个pitch有返回值则会跳过读取资源和自己的loader。假设有use: [loader1,loader2,loader3],三个loader都包含pitchloader和normal loader。
153 | + 第一种情况,三个loader的pitch loader都没有返回值,那么执行顺序为:pitch loader1 -> pitch loader2 -> pitch loader3 -> 获取资源 -> normal loader3 ->
154 | normal loader2 -> normal loader1
155 | + 第二种情况,pitch loader有返回值,假设pitch loader2有返回值,则执行顺序为:pitch loader1 -> pitch loader2 -> noraml loader1
156 |
157 | ```javascript
158 | function loader(source){
159 | console.log('pitchLoader...', source)
160 | }
161 |
162 | loader.raw = true // 如果设置了raw = true,说明当前loader接受的source是个二进制流
163 |
164 | loader.pitch = function(){
165 | console.log('pitch...')
166 | }
167 |
168 |
169 | module.exports = loader
170 | ```
171 |
172 |
173 | ### 异步loader
174 | 同步loader执行完就会继续执行下一个loader
175 | ```javascript
176 | function loader(source, ...rest){
177 | console.log(source)
178 | return source
179 | }
180 | ```
181 | 可以通过在loader中调用
182 | ```javascript
183 | const callback = this.async();
184 | ```
185 | 将loader变成异步loader。异步loader必须手动调用callback才会执行下一个loader
186 | ```javascript
187 | function loader(source){
188 | const callback = this.async(); // 将loader变成异步的
189 | console.log(source)
190 | // 3秒后交给下一个loader处理
191 | setTimeout(() => {
192 | callback(null, source) // 第一个参数用于暴露错误
193 | }, 3000);
194 | return source // return都没用,必须手动调用callback
195 | }
196 | ```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### 源码调试
2 | - 新建debugger.js文件,文件名称可以自定义。引入node_modules/webpack/bin/webpack,使用debug模式运行debugger.js即可
3 |
4 | ### mini-pack目录
5 | 这个目录下的代码就是手写的webpack主流程源码。对应的webpack版本为4.46.0,webpack-cli版本为3.3.12。
6 |
7 | 直接在控制台运行 `node ./mini-pack/cli` 即可。
8 |
9 | 注意,mini-pack中的源码只是webpack主流程的源码,webpack源码本身除了`架构设计`复杂外,还有`业务层面`更加复杂。主流程源码只是针对简单的
10 | 打包场景进行处理,如果遇到打包出错的情况,那么也毫不意外。
11 |
12 | 一般来说,梳理清楚webpack主流程后,基本上对webpack整体架构都会有个清晰的认识。剩下的就是业务复杂度。如果仅仅是对源码感兴趣,大可不必
13 | 深究webpack自身的业务。举个例子,在我手写的过程中,我只是使用了 `index.js`,`test.js`,`a.js`这三个简单的文件进行打包,在真实的webpack
14 | 业务中,光是模块解析,即`require('./test.js')`,为了解析 `test.js` 文件的位置,就已经很复杂了。webpack将解析模块的代码单独封装成一个工具包,
15 | 即`enhanced-resolve`。
16 |
17 | ### loaders目录
18 | loaders目录里面都是各种手写loader,目前手写的 loader 有:
19 | - babel-loader
20 | - file-loader
21 | - url-loader
22 | - less-loader 对应的官方less-loader版本5.0.0
23 | - css-loader 对应的官方css-loader版本5.2.7。其中css loader开发稍稍复杂,可以参考 [css loader原理指南](https://github.com/lizuncong/mini-webpack/blob/master/loaders/css-loader/readme.md)
24 | - style-loader 对应的官方style-loader版本2.0.0
25 | - 如果需要查看 loader 基础知识,可以查看[这篇文章](https://github.com/lizuncong/mini-webpack/blob/master/loaders/loader%E6%8C%87%E5%8D%97.md)
26 | - 如果需要了解 plugin 和 loader 的区别,可以查看[这篇文章](https://github.com/lizuncong/Front-End-Development-Notes/blob/master/%E5%89%8D%E7%AB%AF%E7%9F%A5%E8%AF%86%E4%BD%93%E7%B3%BB(%E5%85%A8%E9%9D%A2)/webpack/loader%E5%92%8Cplugin%E7%9A%84%E5%8C%BA%E5%88%AB.md)
27 |
28 | ### less loader
29 | index.less
30 | ```less
31 | @color: red;
32 | @import './common.less';
33 | body{
34 | background: @color;
35 | background: url('./2.jpg');
36 | }
37 | ```
38 | common.less
39 | ```less
40 | img {
41 | height: 400px;
42 | overflow: hidden;
43 | }
44 | ```
45 | 从打包后的源码可以看出,通过 @import xxx.less 引入的less文件,经过less loader打包,不会单独打包成一个独立的xxx.css module。但是通过 @import xxx.css引入的css文件,则会被打包成一个独立的xxx.css module
46 |
47 | ### plugins目录
48 | plugins目录里面都是各种手写常见的webpack plugin,目前手写的plugin有:
49 | - MediaExtractPlugin。提取CSS文件中多媒体查询CSS并单独打包,在响应式设计系统中能够减少css文件大小,提高加载效率。
50 | - mini-css-extract-plugin 对应的官方版本0.9.0。进行中
51 |
52 |
53 | ### webpack主流程
54 | 在手写webpack主流程源码时,主要以下面的demo为例,在src目录下新建三个文件index.j,test.js,a.js
55 | index.js
56 | ```javascript
57 | const test = require('./test.js')
58 |
59 | console.log(test)
60 | ```
61 | test.js
62 | ```javascript
63 | const a = require('./a.js')
64 |
65 | console.log('a==', a)
66 | console.log('test.js')
67 | module.exports = 'test.js file'
68 | ```
69 | a.js
70 | ```javascript
71 | console.log('a.js')
72 | module.exports = 'a.js file'
73 | ```
74 |
75 | webpack.config.js如下:
76 | ```javascript
77 | const path = require('path');
78 | module.exports = {
79 | mode: "development",
80 | entry: './src/index.js',
81 | devtool: "none",
82 | output: {
83 | filename: '[name].js',
84 | path: path.resolve(__dirname, 'dist')
85 | },
86 | module: {
87 | rules: [
88 | {
89 | test: /\.js$/,
90 | use: {
91 | loader: path.resolve(__dirname, 'loaders', 'loader1'),
92 | }
93 | },
94 | ]
95 | },
96 | plugins: []
97 | };
98 | ```
99 |
100 | #### 理论知识
101 | 在阅读webpack源码前,一定要对 `webpack/tapable` 用法非常熟悉。熟悉了 `tapable` 用法后,对于阅读webpack源码难度降低了一半。如果对 `tapable`
102 | 不熟,可以看我手写的 [mini-tapable](https://github.com/lizuncong/mini-tapable)
103 |
104 | #### 思考以下几个问题
105 | - webpack options默认值是什么时候设置的
106 | - webpack 插件初始化是什么时候设置的
107 | - chunk 和 module的关联是咋样的
108 | - webpack loader的执行时机
109 | - webpack plugin的执行时机
110 | - webpack是如何解析模块的,即当我们通过require('./test.js')引入一个模块后,webpack是如何查找对应的文件的
111 | - webpack如何解析模块依赖的
112 | - webpack如何构建模块
113 | - webpack模版代码如何生成
114 |
115 | #### webpack主流程图
116 | 当我们运行 `npm run build` 的时候,npm 会从 `./node_modules/webpack/bin/webpack` 开始,找到并执行 `webpack-cli/bin/cli.js`,这就是webpack编译的入口文件
117 | 
118 |
119 | #### 打包阶段
120 | webpack打包可以分为几个阶段
121 | - 初始化options默认值及钩子
122 | - 模块解析
123 | - 打包后的代码生成
124 | ### webpack.js
125 | webpack.js主要做的事情如下,其中重点关注下WebpackOptionsApply.js,webpack整个生命周期内的插件基本都在这里初始化
126 | - new WebpackOptionsDefaulter().process(options)。初始化options默认值
127 | - new Compiler(options.context)。初始化compiler实例
128 | - new NodeEnvironmentPlugin().apply(compiler)。为compiler实例设置读写文件系统,这个读写文件系统贯穿整个webpack生命周期
129 | - options.plugins.forEach(plugin => { plugin.apply(compiler) })。注册用户通过webpack.config.js引入的插件
130 | - new WebpackOptionsApply().process(options, compiler)。初始化默认的webpack插件。其中以下插件对于webpack主流程比较重要
131 | + new EntryOptionPlugin().apply(compiler)。***这是挂载webpack编译的入口点!!!***
132 | + new SingleEntryPlugin()。*** 监听compiler.hooks.make事件 !!! ***,并且给compilation.dependencyFactories设置对应的moduleFactory
133 | + 其余插件,可以在阅读webpack源码过程中再慢慢回头看这个文件里面的逻辑。
134 |
135 |
136 | ### compiler.js
137 | 逻辑从compiler.js run方法开始看起
138 | - new ResolverFactory()
139 | - new NormalModuleFactory()。这个实例用来解析模块!!所有的模块都会经过这个实例解析
140 | - const compilation = this.newCompilation(params)。初始化compilation实例,为compilation添加normalModuleFactory,这个主要用来创建enhanced-resolver实例,解析模块
141 | - 调用this.hooks.make钩子开始从入口模块编译。这里会从 `SingleEntryPlugin` 注册的插件开始执行,调用 `compilation.addEntry(context, dep, name, callback)`
142 |
143 | ### compilation.js
144 | webpack在打包时,会从webpack.config.js指定的entry开始构建模块,对每一个entry,调用compilation.entry方法开始构建。
145 | - addEntry()。添加入口点
146 | + 往this._preparedEntrypoints中添加entry,_preparedEntrypoints后面生成chunk时会用到.
147 | - _addModuleChain()。
--------------------------------------------------------------------------------
/mini-pack/lib/buildChunkGraph.js:
--------------------------------------------------------------------------------
1 |
2 | // 从modules及其依赖中提取简化信息
3 | const extraceBlockInfoMap = compilation => {
4 | const blockInfoMap = new Map();
5 | let blockInfoModules;
6 |
7 | // 遍历compilation.modules,对每一个module,遍历module.dependencies
8 | // 获取每个依赖对应的module,构造module和module所依赖的模块的对应关系
9 | for (const module of compilation.modules) {
10 | blockInfoModules = new Set();
11 |
12 | for (const dep of module.dependencies) {
13 | blockInfoModules.add(dep.module)
14 | };
15 |
16 | const blockInfo = {
17 | modules: blockInfoModules, // 是一个集合,保存着模块所依赖的模块
18 | blocks: [] // 对主流程来讲是个空数组
19 | };
20 | blockInfoMap.set(module, blockInfo);
21 |
22 | }
23 | return blockInfoMap;
24 | };
25 |
26 |
27 | // 对每一个入口entry对应的入口模块,从入口模块开始,
28 | // 递归处理依赖模块
29 | // 1.关联入口chunk和module。遍历每一个module,将其依赖模块添加进入口chunk中
30 | // 2.
31 | const visitModules = (
32 | compilation,
33 | inputChunkGroups,
34 | chunkGroupInfoMap,
35 | ) => {
36 |
37 | const blockInfoMap = extraceBlockInfoMap(compilation);
38 |
39 | const chunkGroupCounters = new Map();
40 | for (const chunkGroup of inputChunkGroups) {
41 | chunkGroupCounters.set(chunkGroup, {
42 | index: 0,
43 | index2: 0
44 | });
45 | }
46 |
47 | let nextFreeModuleIndex = 0;
48 | let nextFreeModuleIndex2 = 0;
49 |
50 |
51 | // 添加模块,进入模块,处理模块,离开模块
52 | const ADD_AND_ENTER_MODULE = 0;
53 | const ENTER_MODULE = 1;
54 | const PROCESS_BLOCK = 2;
55 | const LEAVE_MODULE = 3;
56 |
57 | const reduceChunkGroupToQueueItem = (queue, chunkGroup) => {
58 | for (const chunk of chunkGroup.chunks) {
59 | const module = chunk.entryModule;
60 | queue.push({
61 | action: ENTER_MODULE,
62 | block: module,
63 | module,
64 | chunk,
65 | chunkGroup
66 | });
67 | }
68 | chunkGroupInfoMap.set(chunkGroup, {
69 | chunkGroup,
70 | minAvailableModules: new Set(),
71 | minAvailableModulesOwned: true,
72 | availableModulesToBeMerged: [],
73 | skippedItems: [],
74 | resultingAvailableModules: undefined,
75 | children: undefined
76 | });
77 | return queue;
78 | };
79 | // 将inputChunkGroups里面的chunks扁平化
80 | let queue = inputChunkGroups
81 | .reduce(reduceChunkGroupToQueueItem, [])
82 | .reverse();
83 |
84 | let module;
85 | let chunk;
86 | let block;
87 | let chunkGroup;
88 | let chunkGroupInfo;
89 |
90 | while (queue.length) {
91 | while(queue.length){
92 | const queueItem = queue.pop();
93 | // block和module引用的都是module对象
94 | module = queueItem.module;
95 | block = queueItem.block;
96 | chunk = queueItem.chunk;
97 | if (chunkGroup !== queueItem.chunkGroup) {
98 | chunkGroup = queueItem.chunkGroup;
99 | chunkGroupInfo = chunkGroupInfoMap.get(chunkGroup);
100 | }
101 |
102 | switch (queueItem.action) {
103 | case ADD_AND_ENTER_MODULE: {
104 |
105 | // We connect Module and Chunk when not already done
106 | if (chunk.addModule(module)) {
107 | module.addChunk(chunk);
108 | } else {
109 | // already connected, skip it
110 | break;
111 | }
112 | }
113 | // fallthrough
114 | case ENTER_MODULE: {
115 | if (chunkGroup !== undefined) {
116 | const index = chunkGroup.getModuleIndex(module);
117 | if (index === undefined) {
118 | chunkGroup.setModuleIndex(
119 | module,
120 | chunkGroupCounters.get(chunkGroup).index++
121 | );
122 | }
123 | }
124 | if (module.index === null) {
125 | module.index = nextFreeModuleIndex++;
126 | }
127 |
128 | queue.push({
129 | action: LEAVE_MODULE,
130 | block,
131 | module,
132 | chunk,
133 | chunkGroup
134 | });
135 | }
136 | // fallthrough
137 | case PROCESS_BLOCK: {
138 | // get prepared block info
139 | const blockInfo = blockInfoMap.get(block);
140 |
141 | // Buffer items because order need to be reverse to get indicies correct
142 | const skipBuffer = [];
143 | const queueBuffer = [];
144 | // Traverse all referenced modules
145 | for (const refModule of blockInfo.modules) {
146 | // enqueue the add and enter to enter in the correct order
147 | // this is relevant with circular dependencies
148 | queueBuffer.push({
149 | action: ADD_AND_ENTER_MODULE,
150 | block: refModule,
151 | module: refModule,
152 | chunk,
153 | chunkGroup
154 | });
155 | }
156 |
157 | for (let i = queueBuffer.length - 1; i >= 0; i--) {
158 | queue.push(queueBuffer[i]);
159 | }
160 |
161 | break;
162 | }
163 | case LEAVE_MODULE: {
164 | if (chunkGroup !== undefined) {
165 | const index = chunkGroup.getModuleIndex2(module);
166 | if (index === undefined) {
167 | chunkGroup.setModuleIndex2(
168 | module,
169 | chunkGroupCounters.get(chunkGroup).index2++
170 | );
171 | }
172 | }
173 |
174 | if (module.index2 === null) {
175 | module.index2 = nextFreeModuleIndex2++;
176 | }
177 |
178 | break;
179 | }
180 | }
181 | }
182 | }
183 | };
184 |
185 |
186 | const buildChunkGraph = (compilation, inputChunkGroups) => {
187 | const chunkGroupInfoMap = new Map();
188 |
189 |
190 | // PART ONE, 为inputChunkGroups即entry point的_moduleIndices,_moduleIndices2
191 | // 赋值
192 | visitModules(
193 | compilation,
194 | inputChunkGroups,
195 | chunkGroupInfoMap
196 | );
197 | };
198 |
199 | module.exports = buildChunkGraph;
200 |
--------------------------------------------------------------------------------
/loaders/css-loader/readme.md:
--------------------------------------------------------------------------------
1 | ### 前置知识
2 | - [postcss插件开发基础](https://github.com/postcss/postcss/blob/main/docs/writing-a-plugin.md)。不需要了解太深入,了解基础便于阅读源码。
3 | - [webpack loader开发基础](https://webpack.js.org/contribute/writing-a-loader/)
4 |
5 | ### 示例代码
6 | 本篇所有讲解基于下面的代码文件:
7 |
8 | index.css:
9 | ```css
10 | body{
11 | color: blue;
12 | background: yellow;
13 | background: url('./2.png');
14 | }
15 | @import './common.css';
16 | .container{
17 | color: red;
18 | background: url(./2.jpg);
19 | }
20 | ```
21 | common.css:
22 | ```css
23 | div {
24 | height: 200px;
25 | color: red;
26 | overflow: hidden;
27 | }
28 | ```
29 | webpack.config.js文件:
30 | ```javascript
31 | const path = require('path');
32 | const HtmlWebpackPlugin = require('html-webpack-plugin')
33 | module.exports = {
34 | mode: "development",
35 | entry: './src/index.js',
36 | devtool: "source-map",
37 | output: {
38 | filename: '[name].js',
39 | path: path.resolve(__dirname, 'dist')
40 | },
41 | resolveLoader: {
42 | modules: ['node_modules', path.resolve(__dirname, 'loaders')],
43 | },
44 | module: {
45 | rules: [
46 | {
47 | test: /\.js$/,
48 | use: {
49 | loader: path.resolve(__dirname, 'loaders', 'babel-loader'),
50 | options: {
51 | presets: [
52 | '@babel/preset-env'
53 | ]
54 | }
55 | }
56 | },
57 | {
58 | test: /\.(png|jpg)$/,
59 | use: path.resolve(__dirname, 'loaders', 'file-loader')
60 | },
61 | {
62 | test: /\.css$/,
63 | use: [
64 | path.resolve(__dirname, 'loaders', 'css-loader'),
65 | // {
66 | // loader: 'css-loader',
67 | // options: {
68 | // sourceMap: false
69 | // }
70 | // },
71 | ]
72 | }
73 | ]
74 | },
75 | plugins: [
76 | new HtmlWebpackPlugin({
77 | template: "./index.html"
78 | })
79 | ]
80 | };
81 | ```
82 |
83 | 由于还没引入 style loader,因此需要在index.js文件中手动引用 css loader 打包的结果
84 |
85 | index.js文件
86 | ```javascript
87 | const css = require('./index.css')
88 | console.log('css...module', css)
89 | console.log('css...', css.default.toString())
90 | const ele = document.createElement('div')
91 | ele.innerHTML = 'hello world';
92 | document.body.appendChild(ele)
93 | const styleEle = document.createElement('style')
94 | styleEle.appendChild(document.createTextNode(css.default.toString()))
95 | document.head.appendChild(styleEle)
96 | ```
97 | ### css loader原理
98 | css loader用于解析css文件中 `@import`、`url()`等语法。将其解析成通过 `import/require()`引入的模块。css loader使用 `postcss` 将css源码转换成抽象语法树,通过 `AtRule` 监听 `@import` 规则,通过 `Declaration` 监听值包含 `url()` 的
99 | 声明。
100 |
101 | ### css loader最终输出结果是个啥
102 | index.css 经过 css loader 处理后,结果如下:
103 | ```javascript
104 | // Imports
105 | import ___CSS_LOADER_API_IMPORT___ from "../loaders/css-loader/runtime/api.js";
106 | import ___CSS_LOADER_AT_RULE_IMPORT_0___ from "./common.css";
107 | import ___CSS_LOADER_GET_URL_IMPORT___ from "../loaders/css-loader/runtime/getUrl.js";
108 | import ___CSS_LOADER_URL_IMPORT_0___ from "./2.png";
109 | import ___CSS_LOADER_URL_IMPORT_1___ from "./2.jpg";
110 | var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]});
111 | ___CSS_LOADER_EXPORT___.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
112 | var ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___);
113 | var ___CSS_LOADER_URL_REPLACEMENT_1___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_1___);
114 | // Module
115 | ___CSS_LOADER_EXPORT___.push([module.id, "body{\n color: blue;\n background: yellow;\n background: url(" + ___CSS_LOADER_URL_REPLACEMENT_0___ + ");\n}\n\n.container{\n color: red;\n background: url(" + ___CSS_LOADER_URL_REPLACEMENT_1___ + ");\n}\n\n", ""]);
116 | // Exports
117 | export default ___CSS_LOADER_EXPORT___;
118 | ```
119 |
120 | common.css 经过 css loader 处理后,结果如下:
121 | ```javascript
122 | // Imports
123 | import ___CSS_LOADER_API_IMPORT___ from "../loaders/css-loader/runtime/api.js";
124 | var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]});
125 | // Module
126 | ___CSS_LOADER_EXPORT___.push([module.id, "div {\n height: 200px;\n color: red;\n overflow: hidden;\n}\n", ""]);
127 | // Exports
128 | export default ___CSS_LOADER_EXPORT___;
129 | ```
130 |
131 | 经过css loader处理的css文件,最终导出的是一个数组 `list`,也就是上面的 `___CSS_LOADER_EXPORT___`。list是一个二维数组,`list[i]`第一项固定为 css 模块的id,默认为 `@import`的路径。
132 | `list[i]` 第二项固定为 css 模块的源码。
133 |
134 |
135 | 在index.css文件中,通过@import引用了common.css,因此可以看出经过css loader处理后,index.css 的 @import 源码被解析成
136 | ```javascript
137 | import ___CSS_LOADER_AT_RULE_IMPORT_0___ from "./common.css";
138 | ```
139 | 并通过
140 | ```javascript
141 | ___CSS_LOADER_EXPORT___.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
142 | ```
143 | 往index.css的list中添加一个模块。
144 |
145 | `background: url(./2.jpg);` 以及 `background: url('./2.png');` 被解析成
146 | ```javascript
147 | import ___CSS_LOADER_URL_IMPORT_0___ from "./2.png";
148 | import ___CSS_LOADER_URL_IMPORT_1___ from "./2.jpg";
149 |
150 | var ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___);
151 | var ___CSS_LOADER_URL_REPLACEMENT_1___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_1___);
152 | ```
153 |
154 | 然后通过 `background: url(" + ___CSS_LOADER_URL_REPLACEMENT_0___ + ")`引用
155 |
156 |
157 | 如果我们在index.js中打印index.css的输出,会得到:
158 | ```javascript
159 | `div {
160 | height: 200px;
161 | color: red;
162 | overflow: hidden;
163 | }
164 | body{
165 | color: blue;
166 | background: yellow;
167 | background: url(2.131f3c4fe7dda6ea7686ecff63b51db8.png);
168 | }
169 | .container{
170 | color: red;
171 | background: url(2.e85ae876a8234ed3870b97aede7595e0.jpg);
172 | }`
173 | ```
174 | 可以看出,不论 `@import` 出现在 `index.css` 中的什么位置,`common.css` 模块的代码都在 `index.css` 上面。
175 |
176 |
177 | #### @import规则的处理
178 | `postcss-import-parser` 解析 `@import` 规则,并使用变量 `importPluginImports` 收集需要引入的 css 模块路径信息。使用 `importPluginApi` 收集
179 | 需要引入的模块
180 | ```javascript
181 | // 注意:如果index.css中使用了多次 @import './common.css';那么importPluginImports只会保留一个import
182 | const importPluginImports = [
183 | {
184 | importName: '___CSS_LOADER_AT_RULE_IMPORT_0___',
185 | url: '"./common.css"',
186 | index: 0
187 | }
188 | ]
189 | // 后面通过遍历importPluginImports中的每一项,输出 import importName from url:
190 | import ___CSS_LOADER_AT_RULE_IMPORT_0___ from "./common.css";
191 |
192 |
193 |
194 |
195 | // 注意:和importPluginImports不同,如果index.css中多次使用了@import './common.css'; 则这些@import都会被收集到importPluginApi中。
196 | const importPluginApi = [ { importName: '___CSS_LOADER_AT_RULE_IMPORT_0___', index: 0 } ]
197 | // 通过遍历importPluginApi,输出___CSS_LOADER_EXPORT___.i(${item.importName})
198 | ___CSS_LOADER_EXPORT___.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
199 | ___CSS_LOADER_EXPORT___.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
200 | ```
201 |
202 | #### url规则的处理
203 | `postcss-url-parser` 解析 `url()`规则。并使用变量 `urlPluginImports` 收集需要引入的模块路径信息。
204 | ```javascript
205 | const urlPluginImports = [
206 | {
207 | importName: '___CSS_LOADER_GET_URL_IMPORT___',
208 | url: '"../loaders/css-loader/runtime/getUrl.js"',
209 | index: -1
210 | },
211 | {
212 | importName: '___CSS_LOADER_URL_IMPORT_0___',
213 | url: '"./2.png"',
214 | index: 0
215 | },
216 | {
217 | importName: '___CSS_LOADER_URL_IMPORT_1___',
218 | url: '"./2.jpg"',
219 | index: 1
220 | }
221 | ]
222 | // 遍历urlPluginImports输出 import importName from url:
223 | import ___CSS_LOADER_GET_URL_IMPORT___ from "../loaders/css-loader/runtime/getUrl.js";
224 | import ___CSS_LOADER_URL_IMPORT_0___ from "./2.png";
225 | import ___CSS_LOADER_URL_IMPORT_1___ from "./2.jpg";
226 |
227 |
228 |
229 | // 占位信息, postcss-url-paser将 background: url(./2.jpg); 解析成 background: url(___CSS_LOADER_URL_REPLACEMENT_0___);其中___CSS_LOADER_URL_REPLACEMENT_0___
230 | // 用于占位。后面需要使用这个占位符将 background: url(___CSS_LOADER_URL_REPLACEMENT_0___); 解析成运行时代码 "background: url(" + ___CSS_LOADER_URL_REPLACEMENT_0___ + ")";
231 | const replacements = [
232 | {
233 | replacementName: '___CSS_LOADER_URL_REPLACEMENT_0___',
234 | importName: '___CSS_LOADER_URL_IMPORT_0___'
235 | },
236 | {
237 | replacementName: '___CSS_LOADER_URL_REPLACEMENT_1___',
238 | importName: '___CSS_LOADER_URL_IMPORT_1___'
239 | }
240 | ]
241 |
242 |
243 | ```
--------------------------------------------------------------------------------
/mini-pack/lib/MainTemplate.js:
--------------------------------------------------------------------------------
1 | const {
2 | Tapable,
3 | SyncWaterfallHook,
4 | SyncHook,
5 | SyncBailHook
6 | } = require("tapable");
7 | const Template = require('./Template')
8 | const OriginalSource = require('../webpack-sources/OriginalSource')
9 | const ConcatSource = require('../webpack-sources/ConcatSource')
10 | const PrefixSource = require('../webpack-sources/PrefixSource')
11 | const RawSource = require('../webpack-sources/RawSource')
12 | // require function shortcuts:
13 | // __webpack_require__.s = the module id of the entry point
14 | // __webpack_require__.c = the module cache
15 | // __webpack_require__.m = the module functions
16 | // __webpack_require__.p = the bundle public path
17 | // __webpack_require__.i = the identity function used for harmony imports
18 | // __webpack_require__.e = the chunk ensure function
19 | // __webpack_require__.d = the exported property define getter function
20 | // __webpack_require__.o = Object.prototype.hasOwnProperty.call
21 | // __webpack_require__.r = define compatibility on export
22 | // __webpack_require__.t = create a fake namespace object
23 | // __webpack_require__.n = compatibility get default export
24 | // __webpack_require__.h = the webpack hash
25 | // __webpack_require__.w = an object containing all installed WebAssembly.Instance export objects keyed by module id
26 | // __webpack_require__.oe = the uncaught error handler for the webpack runtime
27 | // __webpack_require__.nc = the script nonce
28 |
29 | module.exports = class MainTemplate extends Tapable {
30 |
31 | constructor(outputOptions) {
32 | super();
33 | this.outputOptions = outputOptions || {};
34 | this.hooks = {
35 | assetPath: new SyncWaterfallHook(["path", "options", "assetInfo"]),
36 | hash: new SyncHook(["hash"]),
37 | renderManifest: new SyncWaterfallHook(["result", "options"]),
38 | // bootstrap: new SyncWaterfallHook([
39 | // "source",
40 | // "chunk",
41 | // "hash",
42 | // "moduleTemplate",
43 | // "dependencyTemplates"
44 | // ]),
45 | modules: new SyncWaterfallHook([
46 | "modules",
47 | "chunk",
48 | "hash",
49 | "moduleTemplate",
50 | "dependencyTemplates"
51 | ]),
52 | render: new SyncWaterfallHook([
53 | "source",
54 | "chunk",
55 | "hash",
56 | "moduleTemplate",
57 | "dependencyTemplates"
58 | ]),
59 | moduleObj: new SyncWaterfallHook([
60 | "source",
61 | "chunk",
62 | "hash",
63 | "moduleIdExpression"
64 | ]),
65 | startup: new SyncWaterfallHook(["source", "chunk", "hash"]),
66 | requireExtensions: new SyncWaterfallHook(["source", "chunk", "hash"]),
67 | require: new SyncWaterfallHook(["source", "chunk", "hash"]),
68 | localVars: new SyncWaterfallHook(["source", "chunk", "hash"]),
69 | };
70 | this.hooks.localVars.tap("MainTemplate", (source, chunk, hash) => {
71 | return Template.asString([
72 | source,
73 | "// The module cache",
74 | "var installedModules = {};"
75 | ]);
76 | });
77 | this.hooks.require.tap("MainTemplate", (source, chunk, hash) => {
78 | return Template.asString([
79 | source,
80 | "// Check if module is in cache",
81 | "if(installedModules[moduleId]) {",
82 | Template.indent("return installedModules[moduleId].exports;"),
83 | "}",
84 | "// Create a new module (and put it into the cache)",
85 | "var module = installedModules[moduleId] = {",
86 | Template.indent(this.hooks.moduleObj.call("", chunk, hash, "moduleId")),
87 | "};",
88 | "",
89 | Template.asString([
90 | "// Execute the module function",
91 | `modules[moduleId].call(module.exports, module, module.exports, ${this.renderRequireFunctionForModule(
92 | hash,
93 | chunk,
94 | "moduleId"
95 | )});`
96 | ]
97 | ),
98 | "",
99 | "// Flag the module as loaded",
100 | "module.l = true;",
101 | "",
102 | "// Return the exports of the module",
103 | "return module.exports;"
104 | ]);
105 | });
106 | this.hooks.moduleObj.tap(
107 | "MainTemplate",
108 | (source, chunk, hash, varModuleId) => {
109 | return Template.asString(["i: moduleId,", "l: false,", "exports: {}"]);
110 | }
111 | );
112 | this.hooks.requireExtensions.tap("MainTemplate", (source, chunk, hash) => {
113 | const buf = [];
114 | buf.push("");
115 | buf.push("// expose the modules object (__webpack_modules__)");
116 | buf.push(`${this.requireFn}.m = modules;`);
117 |
118 | buf.push("");
119 | buf.push("// expose the module cache");
120 | buf.push(`${this.requireFn}.c = installedModules;`);
121 |
122 | buf.push("");
123 | buf.push("// define getter function for harmony exports");
124 | buf.push(`${this.requireFn}.d = function(exports, name, getter) {`);
125 | buf.push(
126 | Template.indent([
127 | `if(!${this.requireFn}.o(exports, name)) {`,
128 | Template.indent([
129 | "Object.defineProperty(exports, name, { enumerable: true, get: getter });"
130 | ]),
131 | "}"
132 | ])
133 | );
134 | buf.push("};");
135 |
136 | buf.push("");
137 | buf.push("// define __esModule on exports");
138 | buf.push(`${this.requireFn}.r = function(exports) {`);
139 | buf.push(
140 | Template.indent([
141 | "if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {",
142 | Template.indent([
143 | "Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });"
144 | ]),
145 | "}",
146 | "Object.defineProperty(exports, '__esModule', { value: true });"
147 | ])
148 | );
149 | buf.push("};");
150 |
151 | buf.push("");
152 | buf.push("// create a fake namespace object");
153 | buf.push("// mode & 1: value is a module id, require it");
154 | buf.push("// mode & 2: merge all properties of value into the ns");
155 | buf.push("// mode & 4: return value when already ns object");
156 | buf.push("// mode & 8|1: behave like require");
157 | buf.push(`${this.requireFn}.t = function(value, mode) {`);
158 | buf.push(
159 | Template.indent([
160 | `if(mode & 1) value = ${this.requireFn}(value);`,
161 | `if(mode & 8) return value;`,
162 | "if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;",
163 | "var ns = Object.create(null);",
164 | `${this.requireFn}.r(ns);`,
165 | "Object.defineProperty(ns, 'default', { enumerable: true, value: value });",
166 | "if(mode & 2 && typeof value != 'string') for(var key in value) " +
167 | `${this.requireFn}.d(ns, key, function(key) { ` +
168 | "return value[key]; " +
169 | "}.bind(null, key));",
170 | "return ns;"
171 | ])
172 | );
173 | buf.push("};");
174 |
175 | buf.push("");
176 | buf.push(
177 | "// getDefaultExport function for compatibility with non-harmony modules"
178 | );
179 | buf.push(this.requireFn + ".n = function(module) {");
180 | buf.push(
181 | Template.indent([
182 | "var getter = module && module.__esModule ?",
183 | Template.indent([
184 | "function getDefault() { return module['default']; } :",
185 | "function getModuleExports() { return module; };"
186 | ]),
187 | `${this.requireFn}.d(getter, 'a', getter);`,
188 | "return getter;"
189 | ])
190 | );
191 | buf.push("};");
192 |
193 | buf.push("");
194 | buf.push("// Object.prototype.hasOwnProperty.call");
195 | buf.push(
196 | `${this.requireFn}.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };`
197 | );
198 |
199 | const publicPath = ''
200 | buf.push("");
201 | buf.push("// __webpack_public_path__");
202 | buf.push(`${this.requireFn}.p = ${JSON.stringify(publicPath)};`);
203 | return Template.asString(buf);
204 | });
205 | this.hooks.startup.tap("MainTemplate", (source, chunk, hash) => {
206 | const buf = [];
207 | if (chunk.entryModule) {
208 | buf.push("// Load entry module and return exports");
209 | buf.push(
210 | `return ${this.renderRequireFunctionForModule(
211 | hash,
212 | chunk,
213 | JSON.stringify(chunk.entryModule.id)
214 | )}(${this.requireFn}.s = ${JSON.stringify(chunk.entryModule.id)});`
215 | );
216 | }
217 | return Template.asString(buf);
218 | });
219 | this.hooks.render.tap(
220 | "MainTemplate",
221 | (bootstrapSource, chunk, hash, moduleTemplate, dependencyTemplates) => {
222 | const source = new ConcatSource();
223 | source.add("/******/ (function(modules) { // webpackBootstrap\n");
224 |
225 | // PrefixSource的目标是将\t制表符替换成/******/
226 | source.add(new PrefixSource("/******/", bootstrapSource));
227 | source.add("/******/ })\n");
228 | source.add(
229 | "/************************************************************************/\n"
230 | );
231 | source.add("/******/ (");
232 | // 在JavascriptModulesPlugin中注册this.hooks.modules插件
233 | source.add(
234 | this.hooks.modules.call(
235 | new RawSource(""),
236 | chunk,
237 | hash,
238 | moduleTemplate,
239 | dependencyTemplates
240 | )
241 | );
242 | source.add(")");
243 | return source;
244 | }
245 | );
246 | this.requireFn = "__webpack_require__";
247 | }
248 | getRenderManifest(options){
249 | const result = [];
250 | // 在JavascriptModulesPlugin.js中注册renderManifest插件
251 | this.hooks.renderManifest.call(result, options);
252 |
253 | return result;
254 | }
255 |
256 | updateHash(hash) {
257 | hash.update("maintemplate");
258 | hash.update("3");
259 | this.hooks.hash.call(hash);
260 | }
261 |
262 | getAssetPathWithInfo(path, options){
263 | const assetInfo = {};
264 | const newPath = this.hooks.assetPath.call(path, options, assetInfo);
265 | return { path: newPath, info: assetInfo }
266 | }
267 | getAssetPath(path, options) {
268 | return this.hooks.assetPath.call(path, options);
269 | }
270 | renderBootstrap(hash, chunk, moduleTemplate, dependencyTemplates){
271 | const buf = [''];
272 | buf.push(this.hooks.localVars.call("", chunk, hash));
273 | buf.push("");
274 | buf.push("// The require function");
275 | buf.push(`function ${this.requireFn}(moduleId) {`);
276 | buf.push(Template.indent(this.hooks.require.call("", chunk, hash)));
277 | buf.push("}");
278 | buf.push("");
279 | buf.push(
280 | Template.asString(this.hooks.requireExtensions.call("", chunk, hash))
281 | );
282 | buf.push("");
283 | buf.push("");
284 | buf.push(Template.asString(this.hooks.startup.call("", chunk, hash)));
285 | return buf;
286 | }
287 | render(hash, chunk, moduleTemplate, dependencyTemplates) {
288 | const buf = this.renderBootstrap(
289 | hash,
290 | chunk,
291 | moduleTemplate,
292 | dependencyTemplates
293 | );
294 | let source = this.hooks.render.call(
295 | new OriginalSource(
296 | Template.prefix(buf, " \t") + "\n",
297 | "webpack/bootstrap"
298 | ),
299 | chunk,
300 | hash,
301 | moduleTemplate,
302 | dependencyTemplates
303 | );
304 | chunk.rendered = true;
305 | source.children.push(';')
306 | return source
307 | }
308 | renderRequireFunctionForModule(){
309 | return this.requireFn
310 | }
311 | };
312 |
--------------------------------------------------------------------------------
/mini-pack/lib/Compilation.js:
--------------------------------------------------------------------------------
1 | const {
2 | Tapable,
3 | SyncHook
4 | } = require("tapable");
5 | const path = require('path')
6 | const Chunk = require('./Chunk')
7 | const MainTemplate = require("./MainTemplate");
8 | const ChunkTemplate = require('./ChunkTemplate')
9 | const Entrypoint = require('./Entrypoint')
10 | const buildChunkGraph = require('./buildChunkGraph')
11 | const createHash = require('./util/createHash')
12 | const ModuleTemplate = require('./ModuleTemplate')
13 | const CachedSource = require('../webpack-sources/CachedSource')
14 | class Compilation extends Tapable {
15 | constructor(compiler){
16 | super();
17 | this.compiler = compiler
18 | this.resolverFactory = compiler.resolverFactory;
19 | this.options = compiler.options // webpack options
20 | this.outputOptions = this.options && this.options.output;
21 | this.mainTemplate = new MainTemplate(this.outputOptions);
22 | this.chunkTemplate = new ChunkTemplate(this.outputOptions);
23 | this.moduleTemplates = {
24 | javascript: new ModuleTemplate(this.runtimeTemplate, "javascript"),
25 | };
26 | // this.context = compiler.context
27 | this.inputFileSystem = compiler.inputFileSystem
28 | // this.outputFileSystem = compiler.outputFileSystem
29 | this.hooks = {
30 | beforeModuleIds: new SyncHook(["modules"]),
31 | beforeChunkIds: new SyncHook(["chunks"]),
32 | }
33 | // 一般都是通过compiler.hooks.compilation注册插件的方式
34 | // 为 this.dependencyFactories 设置对应的moduleFactory
35 | this.dependencyFactories = new Map();
36 | this._preparedEntrypoints = [];
37 |
38 | // 代表我们的入口,里面放着所有的入口模块
39 | this.entries = []
40 |
41 | this.modules = [] // 这是一个模块的数组,里面都是模块实例
42 | this._modules = new Map(); // 存储的是已经编译过的模块实例
43 | this.namedChunkGroups = new Map();
44 | this.chunkGroups = [];
45 | this.entrypoints = new Map();
46 |
47 | this.chunks = []
48 | this.assets = {}
49 | }
50 |
51 |
52 | // 从webpack.config中entry指定的入口模块(即入口文件)开始打包
53 | addEntry(context, entry, name, callback){
54 | const slot = {
55 | name: name,
56 | request: entry.request,
57 | module: null
58 | };
59 | // 将入口模块添加进this._preparedEntrypoints数组中
60 | this._preparedEntrypoints.push(slot)
61 | this._addModuleChain(
62 | context,
63 | entry,
64 | module => {
65 | // 将编译后的入口模块添加到入口数组中
66 | this.entries.push(module)
67 | },
68 | (err, module) => {
69 | slot.module = module;
70 | callback(null, module)
71 | }
72 | )
73 | }
74 |
75 | _addModuleChain(context, dependency, onModule, callback){
76 | const Dep = dependency.constructor
77 | // 在Compiler.newCompilation中触发this.hooks.compilation.call(compilation, params)钩子执行,
78 | // 从而在SingleEntryPlugin.js中设置了this.dependencyFactories的值
79 | const moduleFactory = this.dependencyFactories.get(Dep)
80 | moduleFactory.create(
81 | {
82 | contextInfo: {
83 | issuer: "",
84 | compiler: this.compiler.name
85 | },
86 | context,
87 | dependencies: [dependency]
88 | },
89 | (err, module) => {
90 | const addModuleResult = this.addModule(module)
91 | onModule(module)
92 | dependency.module = module
93 | if(addModuleResult.build){
94 | this.buildModule(module, false, null, null, err => {
95 | if (addModuleResult.dependencies) {
96 | this.processModuleDependencies(module, err => {
97 | if (err) return callback(err);
98 | callback(null, module);
99 | });
100 | } else {
101 | return callback(null, module);
102 | }
103 | })
104 | }
105 | }
106 | )
107 | }
108 | addModule(module){
109 | const identifier = module.identifier();
110 | // 判断模块是否已经编译过
111 | const alreadyAddedModule = this._modules.get(identifier);
112 | if (alreadyAddedModule) {
113 | return {
114 | module: alreadyAddedModule,
115 | issuer: false,
116 | build: false,
117 | dependencies: false
118 | };
119 | }
120 | this._modules.set(identifier, module);
121 | this.modules.push(module)
122 | return {
123 | module: module,
124 | issuer: true,
125 | build: true,
126 | dependencies: true
127 | };
128 | }
129 | buildModule(module, optional, origin, dependencies, thisCallback) {
130 | module.build(
131 | this.options,
132 | this,
133 | this.resolverFactory.get("normal", module.resolveOptions),
134 | this.inputFileSystem,
135 | error => {
136 | thisCallback();
137 | }
138 | )
139 | }
140 | processModuleDependencies(module, callback){
141 | const sortedDependencies = [];
142 | module.dependencies.forEach(dep => {
143 | const factory = this.dependencyFactories.get(dep.constructor)
144 | sortedDependencies.push({
145 | factory,
146 | dependencies: [dep]
147 | })
148 | })
149 | this.addModuleDependencies(module, sortedDependencies, callback)
150 | }
151 |
152 | addModuleDependencies(module, dependencies, callback){
153 | if(!dependencies.length) callback()
154 | let count = 0;
155 | // console.log('addModuleDependencies==', dependencies)
156 | dependencies.forEach(item => {
157 | const dependencies = item.dependencies;
158 | const factory = item.factory
159 | factory.create(
160 | {
161 | contextInfo: {
162 | issuer: undefined,
163 | compiler: this.compiler.name
164 | },
165 | resolveOptions: module.resolveOptions,
166 | context: module.context,
167 | dependencies: dependencies
168 | },
169 | (err, dependentModule) => {
170 | const addModuleResult = this.addModule(dependentModule);
171 | for (let index = 0; index < dependencies.length; index++) {
172 | const dep = dependencies[index];
173 | dep.module = dependentModule;
174 | }
175 | if (addModuleResult.build) {
176 | this.buildModule(
177 | dependentModule,
178 | false,
179 | null,
180 | null,
181 | err => {
182 | count++;
183 | if(addModuleResult.dependencies){
184 | this.processModuleDependencies(dependentModule, err => {
185 | if(count === dependencies.length){
186 | callback()
187 | }
188 | });
189 | } else {
190 | if(count === dependencies.length){
191 | callback()
192 | }
193 | }
194 | }
195 | )
196 | }
197 | }
198 | )
199 | })
200 | }
201 | finish(callback){
202 | callback();
203 | }
204 | seal(callback){
205 | for (const preparedEntrypoint of this._preparedEntrypoints) {
206 | const module = preparedEntrypoint.module;
207 | const name = preparedEntrypoint.name;
208 | const chunk = this.addChunk(name);
209 | // entrypoint即是一个chunk group,在webpack配置中配了几个入口点,就有几个
210 | // entrypoint对象
211 | const entrypoint = new Entrypoint(name);
212 | entrypoint.setRuntimeChunk(chunk)
213 | entrypoint.addOrigin(null, name, preparedEntrypoint.request);
214 | this.namedChunkGroups.set(name, entrypoint);
215 | this.entrypoints.set(name, entrypoint);
216 | this.chunkGroups.push(entrypoint);
217 | // 此时, entrypoint和chunk都只是根据name创建的一个空对象,还没和module关联。
218 | // 同时 entrypoint和chunk还没关联。
219 |
220 | // 下面两行代码相互关联chunk和entrypoint
221 | entrypoint.pushChunk(chunk)
222 | chunk.addGroup(entrypoint)
223 | // 下面两行代码相互管理chunk和module
224 | module.addChunk(chunk)
225 | chunk.addModule(module)
226 | chunk.entryModule = preparedEntrypoint.module;
227 | chunk.name = name;
228 |
229 | this.assignDepth(module);
230 | }
231 |
232 | buildChunkGraph(
233 | this,
234 | this.chunkGroups.slice()
235 | )
236 |
237 | this.hooks.beforeModuleIds.call(this.modules);
238 | this.hooks.beforeChunkIds.call(this.chunks);
239 | this.createHash();
240 |
241 | // seal阶段的逻辑主要是在createChunkAssets中,这里开始生成模块模版代码
242 | this.createChunkAssets();
243 |
244 | callback()
245 | }
246 | addChunk(name){
247 | const chunk = new Chunk(name)
248 | this.chunks.push(chunk)
249 | return chunk;
250 | }
251 | assignDepth(module){
252 | let depth = 0;
253 | const queue = new Set([module])
254 | module.depth = 0;
255 | for (module of queue) {
256 | queue.delete(module);
257 | depth = module.depth;
258 |
259 | depth++;
260 | for(let i = 0; i < module.dependencies.length; i++){
261 | const dep = module.dependencies[i]
262 | if(dep.module){
263 | queue.add(dep.module)
264 | dep.module.depth = depth
265 | }
266 | }
267 | }
268 | }
269 | // module包含dependency,即dependency是module.dependencies
270 | // 数组中的每一项,这里会为module.dependencies中的每一个dependency
271 | // 创造一个引用
272 | getDependencyReference(module, dependency){
273 | return dependency.getReference();
274 | }
275 | // buildDependencies(module, dependencies){
276 | // module.dependencies = dependencies.map(data => {
277 | // const childModule = normalModuleFactory.create(data)
278 | // return childModule.build(this)
279 | // })
280 | // }
281 |
282 | // seal(callback){
283 | // this.hooks.seal.call();
284 | // this.hooks.beforeChunks.call()
285 | // for(let entryModule of this.entries){
286 | // const chunk = new Chunk(entryModule)
287 | // this.chunks.push(chunk)
288 | // // 只要模块的名字和代码的名字一样,就说明这个模块属于这个代码块
289 | // chunk.modules = this.modules.filter(module => module.name === chunk.name)
290 | // }
291 | // this.hooks.afterChunks.call()
292 | // this.createChunkAssets()
293 | // callback()
294 | // }
295 | createHash(){
296 | const outputOptions = this.outputOptions;
297 | const hashFunction = outputOptions.hashFunction; // md4;
298 | const hashDigest = outputOptions.hashDigest; // hex
299 | const hashDigestLength = outputOptions.hashDigestLength; // 20
300 | const hash = createHash(hashFunction);
301 | this.mainTemplate.updateHash(hash);
302 | this.chunkTemplate.updateHash(hash);
303 | this.moduleTemplates.javascript.updateHash(hash)
304 |
305 | // 为每个module生成hash及renderedHash值
306 | const modules = this.modules;
307 | for (let i = 0; i < modules.length; i++) {
308 | const module = modules[i];
309 | const moduleHash = createHash(hashFunction);
310 | module.updateHash(moduleHash);
311 | module.hash = moduleHash.digest(hashDigest);
312 | module.renderedHash = module.hash.substr(0, hashDigestLength);
313 | }
314 |
315 | // 生成chunk hash
316 | const chunks = this.chunks.slice();
317 | for (let i = 0; i < chunks.length; i++) {
318 | const chunk = chunks[i];
319 | const chunkHash = createHash(hashFunction);
320 | chunk.updateHash(chunkHash);
321 | chunk.hash = chunkHash.digest(hashDigest);
322 | // 上层的hash值
323 | hash.update(chunk.hash);
324 | chunk.renderedHash = chunk.hash.substr(0, hashDigestLength);
325 | }
326 | this.fullHash = hash.digest(hashDigest);
327 | this.hash = this.fullHash.substr(0, hashDigestLength);
328 | }
329 | createChunkAssets(){
330 | const outputOptions = this.outputOptions;
331 | for(let i = 0; i < this.chunks.length; i++){
332 | const chunk = this.chunks[i]
333 | chunk.files = []
334 | const template = this.mainTemplate;
335 | const manifest = template.getRenderManifest({
336 | chunk,
337 | hash: this.hash,
338 | fullHash: this.fullHash,
339 | outputOptions,
340 | moduleTemplates: this.moduleTemplates,
341 | dependencyTemplates: this.dependencyTemplates
342 | });
343 | for (const fileManifest of manifest) {
344 | const cacheName = fileManifest.identifier;
345 | const usedHash = fileManifest.hash;
346 | const filenameTemplate = fileManifest.filenameTemplate;
347 | const pathAndInfo = this.mainTemplate.getAssetPathWithInfo(filenameTemplate, {
348 | ...fileManifest.pathOptions,
349 | hash: this.hash
350 | })
351 | const file = pathAndInfo.path;
352 | const assetInfo = pathAndInfo.info;
353 | const source = fileManifest.render();
354 | const cachedSource = new CachedSource(source);
355 | this.emitAsset(file, cachedSource, assetInfo);
356 | chunk.files.push(file);
357 | }
358 | }
359 | }
360 | emitAsset(file, source, assetInfo = {}) {
361 | this.assets[file] = source;
362 | }
363 |
364 | getPath(filename, data) {
365 | data = data || {};
366 | data.hash = data.hash || this.hash;
367 | return this.mainTemplate.getAssetPath(filename, data);
368 | }
369 |
370 | getAssets() {
371 | const array = [];
372 | for (const assetName of Object.keys(this.assets)) {
373 | if (Object.prototype.hasOwnProperty.call(this.assets, assetName)) {
374 | array.push({
375 | name: assetName,
376 | source: this.assets[assetName],
377 | info: {}
378 | });
379 | }
380 | }
381 | return array;
382 | }
383 |
384 | }
385 |
386 | module.exports = Compilation
--------------------------------------------------------------------------------
/mini-pack/lib/WebpackOptionsDefaulter.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | const isProductionLikeMode = options => {
4 | return options.mode ==="production" || !options.mode;
5 | };
6 |
7 | const isWebLikeTarget = options => {
8 | return options.target ==="web" || options.target ==="webworker";
9 | };
10 |
11 | const getDevtoolNamespace = library => {
12 | // if options.output.library is a string
13 | if (Array.isArray(library)) {
14 | return library.join(".");
15 | } else if (typeof library ==="object") {
16 | return getDevtoolNamespace(library.root);
17 | }
18 | return library ||"";
19 | };
20 | const getProperty = (obj, path) => {
21 | let name = path.split(".");
22 | for (let i = 0; i < name.length - 1; i++) {
23 | obj = obj[name[i]];
24 | if (typeof obj !== "object" || !obj || Array.isArray(obj)) return;
25 | }
26 | return obj[name.pop()];
27 | };
28 | const setProperty = (obj, path, value) => {
29 | let name = path.split(".");
30 | for (let i = 0; i < name.length - 1; i++) {
31 | if (typeof obj[name[i]] !== "object" && obj[name[i]] !== undefined) return;
32 | if (Array.isArray(obj[name[i]])) return;
33 | if (!obj[name[i]]) obj[name[i]] = {};
34 | obj = obj[name[i]];
35 | }
36 | obj[name.pop()] = value;
37 | };
38 | class WebpackOptionsDefaulter{
39 | constructor(){
40 | // Stores configuration for options
41 | this.config = {
42 | cache:'make',
43 | devtool:'make',
44 | infrastructureLogging:'call',
45 | module:'call',
46 | 'module.defaultRules': 'make',
47 | 'module.unsafeCache':'make',
48 | node:'call',
49 | optimization:'call',
50 | 'optimization.checkWasmTypes':'make',
51 | 'optimization.concatenateModules':'make',
52 | 'optimization.flagIncludedChunks':'make',
53 | 'optimization.minimize':'make',
54 | 'optimization.minimizer':'make',
55 | 'optimization.namedChunks':'make',
56 | 'optimization.namedModules':'make',
57 | 'optimization.nodeEnv':'make',
58 | 'optimization.noEmitOnErrors':'make',
59 | 'optimization.occurrenceOrder':'make',
60 | 'optimization.portableRecords':'make',
61 | 'optimization.removeAvailableModules':'make',
62 | 'optimization.runtimeChunk':'call',
63 | 'optimization.sideEffects':'make',
64 | 'optimization.splitChunks.hidePathInfo':'make',
65 | 'optimization.splitChunks.maxAsyncRequests':'make',
66 | 'optimization.splitChunks.maxInitialRequests':'make',
67 | 'optimization.splitChunks.minSize':'make',
68 | 'optimization.usedExports':'make',
69 | output:'call',
70 | 'output.chunkCallbackName':'make',
71 | 'output.chunkFilename':'make',
72 | 'output.devtoolNamespace':'make',
73 | 'output.globalObject':'make',
74 | 'output.hotUpdateFunction':'make',
75 | 'output.jsonpFunction':'make',
76 | 'output.pathinfo':'make',
77 | performance:'call',
78 | 'performance.hints':'make',
79 | resolve:'call',
80 | 'resolve.aliasFields':'make',
81 | 'resolve.cacheWithContext':'make',
82 | 'resolve.ignoreRootsErrors':'make',
83 | 'resolve.mainFields':'make',
84 | 'resolve.preferAbsolute':'make',
85 | 'resolve.roots':'make',
86 | resolveLoader:'call',
87 | 'resolveLoader.cacheWithContext':'make',
88 | };
89 |
90 | // Stores default options settings or functions for computing them
91 | this.defaults = {
92 | entry:"./src",
93 | devtool: options =>
94 | options.mode ==="development" ?"eval" : false,
95 | cache: options => options.mode ==="development",
96 | context: process.cwd(),
97 | target:"web",
98 | module: value => Object.assign({}, value),
99 | "module.unknownContextRequest":".",
100 | "module.unknownContextRegExp": false,
101 | "module.unknownContextRecursive": true,
102 | "module.unknownContextCritical": true,
103 | "module.exprContextRequest":".",
104 | "module.exprContextRegExp": false,
105 | "module.exprContextRecursive": true,
106 | "module.exprContextCritical": true,
107 | "module.wrappedContextRegExp": /.*/,
108 | "module.wrappedContextRecursive": true ,
109 | "module.wrappedContextCritical": false ,
110 | "module.strictExportPresence": false ,
111 | "module.strictThisContextOnImports": false ,
112 | "module.unsafeCache": options => !!options.cache ,
113 | "module.rules": [],
114 | "module.defaultRules": options => [
115 | {
116 | type:"javascript/auto",
117 | resolve: {}
118 | },
119 | {
120 | test: /\.mjs$/i,
121 | type:"javascript/esm",
122 | resolve: {
123 | mainFields:
124 | options.target ==="web" ||
125 | options.target ==="webworker" ||
126 | options.target ==="electron-renderer"
127 | ? ["browser","main"]
128 | : ["main"]
129 | }
130 | },
131 | {
132 | test: /\.json$/i,
133 | type:"json"
134 | },
135 | {
136 | test: /\.wasm$/i,
137 | type:"webassembly/experimental"
138 | }
139 | ] ,
140 | "output": (value, options) => {
141 | if (typeof value ==="string") {
142 | return {
143 | filename: value
144 | };
145 | } else if (typeof value !=="object") {
146 | return {};
147 | } else {
148 | return Object.assign({}, value)
149 | }
150 | } ,
151 |
152 | "output.filename":"[name].js" ,
153 | "output.chunkFilename": options => {
154 | const filename = options.output.filename;
155 | if (typeof filename !=="function") {
156 | const hasName = filename.includes("[name]");
157 | const hasId = filename.includes("[id]");
158 | const hasChunkHash = filename.includes("[chunkhash]");
159 | // Anything changing depending on chunk is fine
160 | if (hasChunkHash || hasName || hasId) return filename;
161 | // Elsewise prefix"[id]." in front of the basename to make it changing
162 | return filename.replace(/(^|\/)([^/]*(?:\?|$))/,"$1[id].$2");
163 | }
164 | return"[id].js";
165 | },
166 | "output.webassemblyModuleFilename":"[modulehash].module.wasm" ,
167 | "output.library":"" ,
168 | "output.hotUpdateFunction": options => {
169 | return "webpackHotUpdate"
170 | // return Template.toIdentifier(
171 | // "webpackHotUpdate" + Template.toIdentifier(options.output.library)
172 | // )
173 | },
174 | "output.jsonpFunction": options => {
175 | return "webpackJsonp"
176 | // return Template.toIdentifier(
177 | // "webpackJsonp" + Template.toIdentifier(options.output.library)
178 | // )
179 | } ,
180 | "output.chunkCallbackName": options => {
181 | return "webpackChunk"
182 | // return Template.toIdentifier(
183 | // "webpackChunk" + Template.toIdentifier(options.output.library)
184 | // )
185 | } ,
186 | "output.globalObject": options => {
187 | switch (options.target) {
188 | case"web":
189 | case"electron-renderer":
190 | case"node-webkit":
191 | return"window";
192 | case"webworker":
193 | return"self";
194 | case"node":
195 | case"async-node":
196 | case"electron-main":
197 | return"global";
198 | default:
199 | return"self";
200 | }
201 | } ,
202 | "output.devtoolNamespace": options => {
203 | return getDevtoolNamespace(options.output.library)
204 | } ,
205 | "output.libraryTarget":"var" ,
206 | "output.path": path.join(process.cwd(),"dist") ,
207 | "output.pathinfo":options => options.mode ==="development",
208 | "output.sourceMapFilename":"[file].map[query]" ,
209 | "output.hotUpdateChunkFilename":"[id].[hash].hot-update.js" ,
210 | "output.hotUpdateMainFilename":"[hash].hot-update.json" ,
211 | "output.crossOriginLoading": false ,
212 | "output.jsonpScriptType": false ,
213 | "output.chunkLoadTimeout": 120000 ,
214 | "output.hashFunction":"md4" ,
215 | "output.hashDigest":"hex" ,
216 | "output.hashDigestLength": 20 ,
217 | "output.devtoolLineToLine": false ,
218 | "output.strictModuleExceptionHandling": false ,
219 | "node": value => {
220 | if (typeof value ==="boolean") {
221 | return value;
222 | } else {
223 | return Object.assign({}, value);
224 | }
225 | },
226 | "node.console": false,
227 | "node.process": true ,
228 | "node.global": true ,
229 | "node.Buffer": true ,
230 | "node.setImmediate": true ,
231 | "node.__filename":"mock" ,
232 | "node.__dirname":"mock" ,
233 | "performance": (value, options) => {
234 | if (value === false) return false;
235 | if (
236 | value === undefined &&
237 | (!isProductionLikeMode(options) || !isWebLikeTarget(options))
238 | )
239 | return false;
240 | return Object.assign({}, value)
241 | },
242 | "performance.maxAssetSize": 250000 ,
243 | "performance.maxEntrypointSize": 250000 ,
244 | "performance.hints": options =>
245 | isProductionLikeMode(options) ?"warning" : false,
246 | "optimization": value => Object.assign({}, value) ,
247 | // TODO webpack 5: Disable by default in a modes
248 | "optimization.removeAvailableModules":
249 | options => options.mode !=="development",
250 | "optimization.removeEmptyChunks": true ,
251 | "optimization.mergeDuplicateChunks": true ,
252 | "optimization.flagIncludedChunks": options =>
253 | isProductionLikeMode(options),
254 | // TODO webpack 5 add `moduleIds:"named"` default for development
255 | // TODO webpack 5 add `moduleIds:"size"` default for production
256 | // TODO webpack 5 remove optimization.occurrenceOrder
257 | "optimization.occurrenceOrder": options =>
258 | isProductionLikeMode(options),
259 | "optimization.sideEffects": options =>
260 | isProductionLikeMode(options),
261 | "optimization.providedExports": true ,
262 | "optimization.usedExports": options =>
263 | isProductionLikeMode(options),
264 | "optimization.concatenateModules": options =>
265 | isProductionLikeMode(options),
266 | "optimization.splitChunks": {} ,
267 | "optimization.splitChunks.hidePathInfo": options => {
268 | return isProductionLikeMode(options)
269 | },
270 | "optimization.splitChunks.chunks":"async" ,
271 | "optimization.splitChunks.minSize": options => {
272 | return isProductionLikeMode(options) ? 30000 : 10000;
273 | } ,
274 | "optimization.splitChunks.minChunks": 1 ,
275 | "optimization.splitChunks.maxAsyncRequests": options => {
276 | return isProductionLikeMode(options) ? 5 : Infinity;
277 | } ,
278 | "optimization.splitChunks.automaticNameDelimiter":"~" ,
279 | "optimization.splitChunks.automaticNameMaxLength": 109 ,
280 | "optimization.splitChunks.maxInitialRequests": options => {
281 | return isProductionLikeMode(options) ? 3 : Infinity;
282 | } ,
283 | "optimization.splitChunks.name": true ,
284 | "optimization.splitChunks.cacheGroups": {} ,
285 | "optimization.splitChunks.cacheGroups.default": {
286 | automaticNamePrefix:"",
287 | reuseExistingChunk: true,
288 | minChunks: 2,
289 | priority: -20
290 | } ,
291 | "optimization.splitChunks.cacheGroups.vendors": {
292 | automaticNamePrefix:"vendors",
293 | test: /[\\/]node_modules[\\/]/,
294 | priority: -10
295 | },
296 | "optimization.runtimeChunk": value => {
297 | if (value ==="single") {
298 | return {
299 | name:"runtime"
300 | };
301 | }
302 | if (value === true || value ==="multiple") {
303 | return {
304 | name: entrypoint => `runtime~${entrypoint.name}`
305 | };
306 | }
307 | return value;
308 | } ,
309 | "optimization.noEmitOnErrors": options =>
310 | isProductionLikeMode(options)
311 | ,
312 | "optimization.checkWasmTypes": options =>
313 | isProductionLikeMode(options)
314 | ,
315 | "optimization.mangleWasmImports": false ,
316 | // TODO webpack 5 remove optimization.namedModules
317 | "optimization.namedModules":
318 | options => options.mode ==="development",
319 | "optimization.hashedModuleIds": false ,
320 | // TODO webpack 5 add `chunkIds:"named"` default for development
321 | // TODO webpack 5 add `chunkIds:"size"` default for production
322 | // TODO webpack 5 remove optimization.namedChunks
323 | "optimization.namedChunks":
324 | options => options.mode ==="development",
325 | "optimization.portableRecords":
326 | options =>
327 | !!(
328 | options.recordsInputPath ||
329 | options.recordsOutputPath ||
330 | options.recordsPath
331 | ),
332 | "optimization.minimize": options =>
333 | isProductionLikeMode(options)
334 | ,
335 | "optimization.minimizer": options => [
336 | {
337 | apply: compiler => {
338 | // Lazy load the Terser plugin
339 | const TerserPlugin = require("terser-webpack-plugin");
340 | const SourceMapDevToolPlugin = require("./SourceMapDevToolPlugin");
341 | new TerserPlugin({
342 | cache: true,
343 | parallel: true,
344 | sourceMap:
345 | (options.devtool && /source-?map/.test(options.devtool)) ||
346 | (options.plugins &&
347 | options.plugins.some(p => p instanceof SourceMapDevToolPlugin))
348 | }).apply(compiler);
349 | }
350 | }
351 | ],
352 | "optimization.nodeEnv": options => {
353 | // TODO: In webpack 5, it should return `false` when mode is `none`
354 | return options.mode ||"production";
355 | } ,
356 | "resolve": value => Object.assign({}, value) ,
357 | "resolve.unsafeCache": true ,
358 | "resolve.modules": ["node_modules"] ,
359 | "resolve.extensions": [".wasm", ".mjs", ".js", ".json"] ,
360 | "resolve.mainFiles": ["index"] ,
361 | "resolve.aliasFields": options => {
362 | if (
363 | options.target ==="web" ||
364 | options.target ==="webworker" ||
365 | options.target ==="electron-renderer"
366 | ) {
367 | return ["browser"];
368 | } else {
369 | return [];
370 | }
371 | } ,
372 | "resolve.mainFields": options => {
373 | if (
374 | options.target ==="web" ||
375 | options.target ==="webworker" ||
376 | options.target ==="electron-renderer"
377 | ) {
378 | return ["browser", "module", "main"];
379 | } else {
380 | return ["module", "main"];
381 | }
382 | } ,
383 | "resolve.cacheWithContext": options => {
384 | return (
385 | Array.isArray(options.resolve.plugins) &&
386 | options.resolve.plugins.length > 0
387 | )
388 | },
389 | "resolve.preferAbsolute":
390 | options => !options.resolve.roots || options.resolve.roots.length === 0,
391 | "resolve.ignoreRootsErrors":
392 | options => !options.resolve.roots || options.resolve.roots.length === 0,
393 | "resolve.roots": options => [options.context] ,
394 | "resolveLoader": value => Object.assign({}, value) ,
395 | "resolveLoader.unsafeCache": true ,
396 | "resolveLoader.mainFields": ["loader", "main"] ,
397 | "resolveLoader.extensions": [".js", ".json"] ,
398 | "resolveLoader.mainFiles": ["index"] ,
399 | "resolveLoader.cacheWithContext": options => {
400 | return (
401 | Array.isArray(options.resolveLoader.plugins) &&
402 | options.resolveLoader.plugins.length > 0
403 | )
404 | } ,
405 | "infrastructureLogging": value => Object.assign({}, value),
406 | "infrastructureLogging.level":"info" ,
407 | "infrastructureLogging.debug": false ,
408 | }
409 | }
410 | process(options){
411 | for (let name in this.defaults) {
412 | switch (this.config[name]) {
413 | /**
414 | * If {@link ConfigType} doesn't specified and current value is `undefined`, then default value will be assigned
415 | */
416 | case undefined:
417 | if (getProperty(options, name) === undefined) {
418 | setProperty(options, name, this.defaults[name]);
419 | }
420 | break;
421 | /**
422 | * Assign result of {@link CallConfigHandler}
423 | */
424 | case "call":
425 | setProperty(
426 | options,
427 | name,
428 | this.defaults[name].call(this, getProperty(options, name), options)
429 | );
430 | break;
431 | /**
432 | * Assign result of {@link MakeConfigHandler}, if current value is `undefined`
433 | */
434 | case "make":
435 | if (getProperty(options, name) === undefined) {
436 | setProperty(options, name, this.defaults[name].call(this, options));
437 | }
438 | break;
439 | /**
440 | * Adding {@link AppendConfigValues} at the end of the current array
441 | */
442 | case "append": {
443 | let oldValue = getProperty(options, name);
444 | if (!Array.isArray(oldValue)) {
445 | oldValue = [];
446 | }
447 | oldValue.push(...this.defaults[name]);
448 | setProperty(options, name, oldValue);
449 | break;
450 | }
451 | default:
452 | throw new Error(
453 | "OptionsDefaulter cannot process " + this.config[name]
454 | );
455 | }
456 | }
457 | return options;
458 | }
459 | }
460 |
461 | module.exports = WebpackOptionsDefaulter
462 |
--------------------------------------------------------------------------------