最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • webpack入口执行流及调试

    正文概述 掘金(shuazi)   2021-02-18   739

    以下内容为个人的理解,如有错漏希望大家指正

    调试配置

    1. clone webpack github项目
    2. 在项目根目录建立debug目录
    3. 创建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()
    		]
    }
    
    1. 创建调试入口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)
        }
    })
    
    1. 新建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入口流程的过程

    自定义插件

    1. 自定义插件本质上是绑定自定义事件到webpack对应的钩子中,webpack运行到对应的钩子时会执行
    2. 如果webpack某个钩子是SyncBailHook类型,则可以在对应订阅事件中加入return true(只要不是undefined),来告诉webpack后续的订阅事件不用运行。

    SyncBailHook.js 实现逻辑
    tapable/lib/HookCodeFactory.js 调用1
    tapable/lib/HookCodeFactory.js 调用2

    1. 自定义插件还可以利用webpack内置插件实现功能避免

    参考文章

    1. Webpack源码解读:理清编译主流程
    2. Webpack tapable 使用研究

    起源地下载网 » webpack入口执行流及调试

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元