以下内容为个人的理解,如有错漏希望大家指正
调试配置
- clone webpack github项目
- 在项目根目录建立debug目录
- 创建config.js简单配置打包项
const path = require('path')
const MinaWebpackPlugin = require('./plugins/MinaWebpackPlugin')
module.exports = {
context: __dirname,
mode: 'development',
devtool: 'source-map',
entry: './src/index.js',
output: {
path: path.join(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/,
}
]
},
plugins: [
new MinaWebpackPlugin()
]
}
- 创建调试入口start.js文件,使用 require webpack 的方式
const webpack = require('../lib/index.js') // 直接使用源码中的webpack函数
const config = require('./webpack.config')
const compiler = webpack(config)
compiler.run((err, stats)=>{
if(err){
console.error(err)
}else{
console.log(stats)
}
})
- 新建vscode 调式配置 launch.json,将program设为start.js地址
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动程序",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\debug\\start.js" //只需更改此处
}
]
}
webpack入口文件
通过package.json 可知入口文件为: webpack/lib/index.js
// webpack/lib/index.js
/**
* @template {Function} T
* @param {function(): T} factory factory function
* @returns {T} function
*/
const lazyFunction = factory => {
const fac = memoize(factory);
const f = /** @type {any} */ ((...args) => {
return fac()(...args);
});
return /** @type {T} */ (f);
};
//mergeExports 将第exports对象所有属性定义在函数fn下,并修改默认的访问器属性及数据属性
/**
* @template A
* @template B
* @param {A} obj input a
* @param {B} exports input b
* @returns {A & B} merged
*/
const mergeExports = (obj, exports) => {
const descriptors = Object.getOwnPropertyDescriptors(exports);
for (const name of Object.keys(descriptors)) {
const descriptor = descriptors[name];
if (descriptor.get) {
const fn = descriptor.get;
Object.defineProperty(obj, name, {
configurable: false,
enumerable: true,
get: memoize(fn)
});
} else if (typeof descriptor.value === "object") {
Object.defineProperty(obj, name, {
configurable: false,
enumerable: true,
writable: false,
value: mergeExports({}, descriptor.value)
});
} else {
throw new Error(
"Exposed values must be either a getter or an nested object"
);
}
}
return /** @type {A & B} */ (Object.freeze(obj));
};
//webpack 函数体;
//lazyFunction 通过 memoize 返回了一个闭包函数;
//此闭包函数中有缓存标识,缓存 webpack 函数是否运行过。
//也就是说首次运行webpack() 会完整走一遍index.js代码,如果有缓存直接读缓存
const fn = lazyFunction(() => require("./webpack"));
//mergeExports 将第二个参数对象的属性定义在了 webpack 函数下,并修改这些数据的默认访问器属性、数据属性行为
//因此 start.js 这段脚本const compiler = webpack(config),运行的还是 require("./webpack")) 导出的方法
module.exports = mergeExports(fn, {
get webpack() {
return require("./webpack");
},
get validate() {
const validateSchema = require("./validateSchema");
const webpackOptionsSchema = require("../schemas/WebpackOptions.json");
return options => validateSchema(webpackOptionsSchema, options);
},
get validateSchema() {
const validateSchema = require("./validateSchema");
return validateSchema;
},
get version() {
return /** @type {string} */ (require("../package.json").version);
},
get cli() {
return require("./cli");
},
get AutomaticPrefetchPlugin() {
return require("./AutomaticPrefetchPlugin");
},
get BannerPlugin() {
return require("./BannerPlugin");
},
get Cache() {
return require("./Cache");
},
get Chunk() {
return require("./Chunk");
},
get ChunkGraph() {
return require("./ChunkGraph");
},
get CleanPlugin() {
return require("./CleanPlugin");
},
get Compilation() {
return require("./Compilation");
},
get Compiler() {
return require("./Compiler");
},
...
});
webpack 函数主体
// webpack/lib/webpack.js
/**
* @param {WebpackOptions[]} childOptions options array
* @returns {MultiCompiler} a multi-compiler
*/
const createMultiCompiler = childOptions => {
const compilers = childOptions.map(options => createCompiler(options));
const compiler = new MultiCompiler(compilers);
for (const childCompiler of compilers) {
if (childCompiler.options.dependencies) {
compiler.setDependencies(
childCompiler,
childCompiler.options.dependencies
);
}
}
return compiler;
};
/**
* @param {WebpackOptions} rawOptions options object
* @returns {Compiler} a compiler
*/
const createCompiler = rawOptions => {
//使用用户配置项赋值所有配置项(标准化、正规化配置),webpack所有的配置都在这找到
const options = getNormalizedWebpackOptions(rawOptions);
//如果配置中的 context 没有值,则给一个默认值:process.cwd()
applyWebpackOptionsBaseDefaults(options);
//Compiler类(./lib/Compiler.js):webpack的主要引擎,在compiler对象记录了完整的webpack环境信息,
//在webpack从启动到结束,compiler只会生成一次。
//可以在compiler对象上读取到webpack config信息,outputPath等;
const compiler = new Compiler(options.context);
compiler.options = options;
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
//获取并调用用户配置中的插件▲▲也就是说用的插件优先级高于内部插件的运行▲▲
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
applyWebpackOptionsDefaults(options);
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
//运行了各种内置插件
new WebpackOptionsApply().process(options, compiler);
compiler.hooks.initialize.call();
return compiler;
};
const webpack = /** @type {WebpackFunctionSingle & WebpackFunctionMulti} */ ((
options,
callback
) => {
const create = () => {
//validateSchema 实际为一个schema-utils包的validate方法,以验证用户webpack配置的参数类型是否通过校验
//例如:我将 config.mode 故意配错成布尔值 true,打包时便会报错
//configuration.mode should be one of these:
//"development" | "production" | "none"
validateSchema(webpackOptionsSchema, options);
/** @type {MultiCompiler|Compiler} */
let compiler;
let watch = false;
/** @type {WatchOptions|WatchOptions[]} */
let watchOptions;
//wepback 配置允许为一个数组,每一个元素为一个配置,createMultiCompiler 会执行一遍每一个配置
//watch、watchOptions 对应webpack的配置项(监控文件修改,自动build) https://webpack.docschina.org/configuration/watch/
if (Array.isArray(options)) {
/** @type {MultiCompiler} */
compiler = createMultiCompiler(options);
watch = options.some(options => options.watch);
watchOptions = options.map(options => options.watchOptions || {});
} else {
/** @type {Compiler} */
compiler = createCompiler(options);
watch = options.watch;
watchOptions = options.watchOptions || {};
}
return { compiler, watch, watchOptions };
};
if (callback) {
try {
const { compiler, watch, watchOptions } = create();
if (watch) {
compiler.watch(watchOptions, callback);
} else {
compiler.run((err, stats) => {
compiler.close(err2 => {
callback(err || err2, stats);
});
});
}
return compiler;
} catch (err) {
process.nextTick(() => callback(err));
return null;
}
} else {
const { compiler, watch } = create();
if (watch) { //callback 设置项必须与 watch 参数同时存在
util.deprecate(
() => {},
"A 'callback' argument need to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.",
"DEP_WEBPACK_WATCH_WITHOUT_CALLBACK"
)();
}
return compiler;
}
});
module.exports = webpack;
以上为一个简单的webpack入口流程的过程
自定义插件
- 自定义插件本质上是绑定自定义事件到webpack对应的钩子中,webpack运行到对应的钩子时会执行
- 如果webpack某个钩子是SyncBailHook类型,则可以在对应订阅事件中加入return true(只要不是undefined),来告诉webpack后续的订阅事件不用运行。
SyncBailHook.js 实现逻辑
tapable/lib/HookCodeFactory.js 调用1
tapable/lib/HookCodeFactory.js 调用2
- 自定义插件还可以利用webpack内置插件实现功能避免
参考文章
- Webpack源码解读:理清编译主流程
- Webpack tapable 使用研究
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!