1.源码细读-深入了解terser-webpack-plugin的码解实现
2.webpack5loader和plugin原理解析
3.Webpack进阶less-loader、css-loader、码解style-loader源码解析
4.webpack 4 源码主流程分析(十一):文件的码解生成
5.一文看懂 webpack 的所有 source map !🤔
6.webpack打包原理 ?码解 看完这篇你就懂了 !
源码细读-深入了解terser-webpack-plugin的实现
terser-webpack-plugin 是一个基于 webpack 的插件,它利用 terser 库对 JavaScript 代码进行压缩和混淆。码解其核心功能在于通过在 webpack 的码解收货人信息填写页面源码运行时钩子 optimizeChunkAssets 中注册,实现了代码优化过程。码解在 apply 函数中,码解它获取 compilation 实例,码解并通过 tapPromise 注册一个异步任务,码解当 webpack 执行优化阶段时,码解每个 chunk 会触发这个任务,码解执行 minify 函数进行压缩处理。码解
optimise 函数是码解实际的任务处理入口,它负责具体的码解优化流程。函数内部,scheduleTask 负责并行处理,如果开启 parallel 模式,会利用jest-worker提供的线程池进行并发工作,线程池管理复杂,根据 node 版本不同采用 worker_threads 或 child_process。minify 函数则是压缩和混淆代码的核心操作,它直接使用 terser 库完成任务。
总的来说,terser-webpack-plugin 的优化流程包括在 webpack 的优化阶段对代码进行压缩,使用 Jest 的 worker 线程池进行并行处理,以及通过 terser 库的实际压缩操作。理解这些核心环节,可以帮助开发者更深入地掌握该插件的使用和工作原理。
webpack5loader和plugin原理解析
大家好,今天为大家解析下loader和plugin一、区别loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、网游问道 源码压缩等,最终一起打包到指定的文件中
plugin赋予了Webpack各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决loader无法实现的其他事从整个运行时机上来看,如下图所示:
可以看到,两者在运行时机上的区别:
loader运行在打包文件之前plugins在整个编译周期都起作用在Webpack运行的生命周期中会广播出许多事件,Plugin可以监听这些事件,在合适的时机通过Webpack提供的API改变输出结果
对于loader,实质是一个转换器,将A文件进行编译形成B文件,操作的是文件,比如将A.scss或A.less转变为B.css,单纯的文件转换过程
下面我们来看看loader和plugin实现的原理
Loader原理loader概念帮助webpack将不同类型的文件转换为webpack可识别的模块。
loader执行顺序分类
pre:前置loader
normal:普通loader
inline:内联loader
post:后置loader
执行顺序
4类loader的执行优级为:pre>normal>inline>post。
相同优先级的loader执行顺序为:从右到左,从下到上。
例如:
//此时loader执行顺序:loader3-loader2-loader1module:{ rules:[{ test:/\.js$/,loader:"loader1",},{ test:/\.js$/,loader:"loader2",},{ test:/\.js$/,loader:"loader3",},],},//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},使用loader的方式
配置方式:在webpack.config.js文件中指定loader。(pre、normal、postloader)
内联方式:在每个import语句中显式指定loader。(inlineloader)
开发一个loader1.最简单的loader//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};它接受要处理的源码作为参数,输出转换后的js代码。
2.loader接受的参数content源文件的内容
mapSourceMap数据
meta数据,可以是任何内容
loader分类1.同步loadermodule.exports=function(content,map,meta){ returncontent;};this.callback方法则更灵活,因为它允许传递多个参数,而不仅仅是content。
module.exports=function(content,map,meta){ //传递map,让source-map不中断//传递meta,让下一个loader接收到其他参数this.callback(null,content,map,meta);return;//当调用callback()函数时,总是返回undefined};2.异步loadermodule.exports=function(content,map,meta){ constcallback=this.async();//进行异步操作setTimeout(()=>{ callback(null,result,map,meta);},);};由于同步计算过于耗时,在Node.js这样的单线程环境下进行此操作并不是好的方案,我们建议尽可能地使你的技能出租源码loader异步化。但如果计算量很小,同步loader也是可以的。
3.RawLoader默认情况下,资源文件会被转化为UTF-8字符串,然后传给loader。通过设置raw为true,loader可以接收原始的Buffer。
module.exports=function(content){ //content是一个Buffer数据returncontent;};module.exports.raw=true;//开启RawLoader4.PitchingLoadermodule.exports=function(content){ returncontent;};module.exports.pitch=function(remainingRequest,precedingRequest,data){ console.log("dosomethings");};webpack会先从左到右执行loader链中的每个loader上的pitch方法(如果有),然后再从右到左执行loader链中的每个loader上的普通loader方法。
在这个过程中如果任何pitch有返回值,则loader链被阻断。webpack会跳过后面所有的的pitch和loader,直接进入上一个loader。
loaderAPI方法名含义用法this.async异步回调loader。返回this.callbackconstcallback=this.async()this.callback可以同步或者异步调用的并返回多个结果的函数this.callback(err,content,sourceMap?,meta?)this.getOptions(schema)获取loader的optionsthis.getOptions(schema)this.emitFile产生一个文件this.emitFile(name,content,sourceMap)this.utils.contextify返回一个相对路径this.utils.contextify(context,request)this.utils.absolutify返回一个绝对路径this.utils.absolutify(context,request)更多文档,请查阅webpack官方loaderapi文档
手写clean-log-loader作用:用来清理js代码中的console.log
//loaders/clean-log-loader.jsmodule.exports=functioncleanLogLoader(content){ //将console.log替换为空returncontent.replace(/console\.log\(.*\);?/g,"");};手写banner-loader作用:给js代码添加文本注释
loaders/banner-loader/index.js
constschema=require("./schema.json");module.exports=function(content){ //获取loader的options,同时对options内容进行校验//schema是options的校验规则(符合JSONschema规则)constoptions=this.getOptions(schema);constprefix=`/**Author:${ options.author}*/`;return`${ prefix}\n${ content}`;};loaders/banner-loader/schema.json
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},0手写babel-loader作用:编译js代码,将ES6+语法编译成ES5-语法。
下载依赖
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},1loaders/babel-loader/index.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},2loaders/banner-loader/schema.json
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},3手写file-loader作用:将文件原封不动输出出去
下载包
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},4loaders/file-loader.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},5loader配置
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},6手写style-loader作用:动态创建style标签,插入js中的样式代码,使样式生效。
loaders/style-loader.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},7Plugin原理Plugin的作用通过插件我们可以扩展webpack,加入自定义的构建行为,使webpack可以执行更广泛的任务,拥有更强的构建能力。
Plugin工作原理webpack就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack通过Tapable来组织这条复杂的生产线。webpack在运行过程中会广播事件,猎头软件源码插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。webpack的事件流机制保证了插件的有序性,使得整个系统扩展性很好。——「深入浅出Webpack」
站在代码逻辑的角度就是:webpack在编译代码过程中,会触发一系列Tapable钩子事件,插件所做的,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当webpack构建的时候,插件注册的事件就会随着钩子的触发而执行了。
Webpack内部的钩子什么是钩子钩子的本质就是:事件。为了方便我们直接介入和控制编译过程,webpack把编译过程中触发的各类关键事件封装成事件接口暴露了出来。这些接口被很形象地称做:hooks(钩子)。开发插件,离不开这些钩子。
TapableTapable为webpack提供了统一的插件接口(钩子)类型定义,它是webpack的核心功能库。webpack中目前有十种hooks,在Tapable源码中可以看到,他们是:
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},8Tapable还统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:
tap:可以注册同步钩子和异步钩子。
tapAsync:回调方式注册异步钩子。
tapPromise:Promise方式注册异步钩子。
Plugin构建对象Compilercompiler对象中保存着完整的Webpack环境配置,每次启动webpack构建时它都是一个独一无二,仅仅会创建一次的对象。
这个对象会在首次启动Webpack时创建,我们可以通过compiler对象上访问到Webapck的主环境配置,比如loader、android equalizer 源码plugin等等配置信息。
它有以下主要属性:
compiler.options可以访问本次启动webpack时候所有的配置文件,包括但不限于loaders、entry、output、plugin等等完整配置信息。
compiler.inputFileSystem和compiler.outputFileSystem可以进行文件操作,相当于Nodejs中fs。
compiler.hooks可以注册tapable的不同种类Hook,从而可以在compiler生命周期中植入不同的逻辑。
compilerhooks文档
Compilationcompilation对象代表一次资源的构建,compilation实例能够访问所有的模块和它们的依赖。
一个compilation对象会对构建依赖图中所有模块,进行编译。在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、分块(chunk)、哈希(hash)和重新创建(restore)。
它有以下主要属性:
compilation.modules可以访问所有模块,打包的每一个文件都是一个模块。
compilation.chunkschunk即是多个modules组成而来的一个代码块。入口文件引入的资源组成一个chunk,通过代码分割的模块又是另外的chunk。
compilation.assets可以访问本次打包生成所有文件的结果。
compilation.hooks可以注册tapable的不同种类Hook,用于在compilation编译模块阶段进行逻辑添加以及修改。
compilationhooks文档
生命周期简图开发一个插件最简单的插件plugins/test-plugin.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},9注册hook//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};0启动调试通过调试查看compiler和compilation对象数据情况。
package.json配置指令
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};1运行指令
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};2此时控制台输出以下内容:
PSC:\Users\\Desktop\source>//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};2>source@1.0.0debug>node--inspect-brk./node_modules/webpack-cli/bin/cli.jsDebuggerlisteningonws://.0.0.1:/ea-7b--a7-fccForhelp,see:/post/开发思路:
我们需要借助html-webpack-plugin来实现
在html-webpack-plugin输出index.html前将内联runtime注入进去
删除多余的runtime文件
如何操作html-webpack-plugin?官方文档
实现:
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};7Webpack进阶less-loader、css-loader、style-loader源码解析
Webpack进阶学习中,Loader的运用是关键环节。在深入理解Loader基础后,本文将解析less-loader、css-loader和style-loader的内部工作原理。
less-loader是专为处理Less样式文件设计的,它将Less代码转换为浏览器能识别的CSS。以less文件为例,其工作原理是调用less库的功能,将扩展了CSS特性的Less代码转化为CSS,如变量、Mixin和函数等。
css-loader的功能则更为复杂,它不仅处理@import和url语句,还支持css-modules,将样式文件内容合并并作为JavaScript模块输出。以多个样式文件(如a.css、b.css和c.css)为例,css-loader会将它们合并成一个JavaScript模块,输出包含所有样式内容的字符串。
style-loader的作用在于将css-loader转化后的CSS样式代码插入到DOM中。理论上,我们可能期望直接在JavaScript中插入CSS代码,但css-loader返回的是模块化的代码,不能直接放入style标签。style-loader的实现通过一种巧妙的方式,将这些模块代码适当地包装,确保样式能正确插入到文档中。
style-loader的设计思路独特,其内部逻辑涉及Loader调用链、执行顺序和模块化输出等多个层面,理解style-loader的运作机制,对于深化对Webpack和Loader的理解至关重要。深入研究这些Loader的源码,无疑能提升你对Webpack进阶应用的掌握程度。
webpack 4 源码主流程分析(十一):文件的生成
本文深入分析了 Webpack 4 中文件生成的具体流程。在资源写入文件阶段,通过一系列优化和处理,最终返回到 Compiler.js 的 compile 方法,其中 Compiler 的属性 _lastCompilationFileDependencies 和 _lastCompilationContextDependencies 被赋予了 fileDependencies 和 contextDependencies。紧随其后的是创建目标文件夹的过程,该操作通过 outputPath 属性配置,结合 mkdirp 函数完成。
在创建目标文件并写入阶段,通过 asyncLib.forEachLimit 方法并行处理每个文件资源,实现路径拼接、源码转换为 buffer,最后写入真实路径的文件。对于不同类型的 source 实例,如 CachedSource、ConcatSource 和 ReplaceSource,其处理逻辑各不相同,但最终目标都是获取替换后的字符串并合并返回 resultStr。所有文件创建写入完成后,执行回调,触发Compiler.afterEmit:hooks,进一步设置 stats 并打印构建信息。
至此,构建流程全部结束。通过本文的分析,我们可以更直观地了解 Webpack 4 中文件生成的具体实现细节,为深入理解 Webpack 的工作原理和优化提供理论支持。本章小结,下章将解析打包后的文件,敬请期待。
一文看懂 webpack 的所有 source map !🤔
深入理解Webpack的各类source map
在前端开发中,source map经常让人感到困惑,尤其在Webpack提供的众多source选项中。本文将逐一探讨它们的不同之处。 source map,就像是编译后的代码与原始源代码之间的导航图,有助于在生产环境中追踪和修复错误。当代码编译后,失去可读性,source map的作用就是通过编译后的代码找到对应的源代码位置,帮助开发者快速定位问题。 在webpack配置中,devtool属性控制source map的生成形式。它支持多种类型,尽管名称多样,但核心机制相似。理解几个关键术语是关键:eval、source-map、inline-source-map、nosources-source-map、hidden-source-map、cheap-source-map和cheap-module-source-map。 以eval为例,它不生成独立的.map文件,而是将映射信息内置于eval函数中,虽然在开发环境中能提供一些帮助,但在生产环境中由于调试体验差而不可取。而source-map则是最标准的形式,生成单独的.map文件,错误信息定位准确,能查看源码。 尝试在项目中故意引入错误,可以看到不同source map类型下的报错信息差异。eval-source-map提供基本的错误定位,但列号可能不准确。而inline-source-map和nosources-source-map则展示了源码信息的完整性和缺失。hidden-source-map和cheap-source-map虽然有映射,但信息可能模糊或处理过。而cheap-module-source-map则定位正确,但无法定位到具体列。 总结来说,理解source map的关键在于理解每个选项背后的含义,如何根据项目需求选择合适的配置。希望这篇文章能帮助你清晰地认识Webpack的source map功能。webpack打包原理 ? 看完这篇你就懂了 !
深入浅出 webpack
webpack 是一个现代 JavaScript 应用程序的静态模块打包器,其本质是对模块进行递归构建依赖关系图,然后打包成一个或多个 bundle。
理解 webpack 的工作流程,像是理解一条生产线,每一步都有其职责,依赖于前一步骤的结果。插件则像是生产线中的功能模块,根据特定时机对资源进行处理。
webpack 通过 Tapable 组织整个生产流程,它在运行中广播事件,插件只需监听感兴趣的事件,即可加入流程,改变整个系统。事件流机制确保了插件的有序性,提高了系统的扩展性。
webpack 的核心概念包括:入口起点(Entry),定义了构建开始的模块;输出(Output),指定生成的 bundle 存储位置;模块(Module),一切皆模块,所有资源被转换为模块;代码块(Chunk),多个模块组合用于代码优化;loader,处理非 JavaScript 文件,将所有类型转换为可引用的模块;插件,执行更广泛任务。
理解 webpack 的构建流程,从入口文件开始,遍历依赖关系,转换为浏览器可执行代码,并生成最终的 bundle。在实践过程中,定义 Compiler 类,使用 babel 解析源代码,遍历 AST 抽象语法树,找出依赖模块,转换为可执行代码,并构建依赖关系图,重写 require 函数以输出 bundle。
完成 webpack 的理解,需要通过实践,例如构建一个简易版本的 webpack。从定义 Compiler 类开始,解析入口文件,使用 babel 解析内部语法,找出依赖模块,将 AST 转换为代码,递归解析所有依赖项,构建依赖关系图,重写 require 函数以输出 bundle。
通过实际操作,可以深入理解 webpack 的 bundle 实现过程,从入口文件执行开始,利用 eval 执行代码,处理依赖引用,生成最终的 bundle。通过这个实践过程,可以全面掌握 webpack 的工作原理和使用方法。