深入浅出Webpack - 1 - 前端构建工具
前端构建⼯具
模块化
-
模块化: 将复杂的项目代码拆分为独立的、可复用的模块,每个模块负责独立的功能,并通过接口与其他模块交互。提高代码的可读性、可维护性、复用性。
-
模块化解决的问题:命名冲突、功能复用、依赖管理、代码复用、按需加载、作用域隔离、可维护性
模块化规范
-
CommonJS
- 核⼼思想:通过require⽅法来同步加载依赖的其他模块,通过module.exports导出需要暴露的接⼝。
// 导⼊ const moduleA = require('./moduleA'); // 导出 module.exports = moduleA.someFunc;
- 分类:CommonJS 分为 CommonJS1 和 CommonJS2
- 区别:
- CommonJS1 只能通过 exports.XX=XX 的⽅式导出。
- CommonJS2 在 CommonJS1 的基础上加⼊了 module.exports=XX 的导出⽅式。CommonJS通常指CommonJS2。
- 优点:
- 代码可复⽤于Node.js环境下并运⾏。
- 通过Npm包采⽤了CommonJS规范。
- 缺点:
- 代码无法直接运行在浏览器环境下,必须通过工具转换成标准的ES5代码。
-
AMD
- 核⼼思想:采⽤了异步的⽅式去加载依赖的模块。AMD规范主要⽤于解决针对浏览器环境的模块化问题
// 定义⼀个模块 define('module', 'dep', function (dep) { return exports }) // 导⼊和使⽤ require(['module'], function (module) { module.someFunction(); })
- 优点:
- 可在不转换代码的情况下直接在浏览器中运⾏。
- 可异步加载依赖。
- 可并⾏加载多个依赖。
- 代码可运⾏在浏览器环境和Node.js环境下。
- 缺点:
- JavaScript运行环境没有原生支持AMD,需要先导入实现了AMD的库后才能正常使用
-
ES6模块
- 国际标准化组织ECMA提出的JavaScript模块化规范,终极模块化⽅案。
// 导⼊ import { hello } from './moduleA.js'; import defaultFunction from './moduleB.js'; // 导出 export function hello() { } export default function () { }
- 缺点:⽬前⽆法直接运⾏在⼤部分JavaScript运⾏环境下,必须通过⼯具转换成标准的ES5后才能正常运⾏。
-
样式⽂件中的模块化
- 以SCSS为例,将⼀些通⽤样式放到⼀个通⽤⽂件中,再在另⼀个⽂件中通过@import语句导⼊和使⽤这些样式⽚段
// utils.scss @mixin center { position: absolute; left: 50 %; top: 50 %; transform: translate(-50 %, -50 %); } // main.scss @import './utils.scss'; #box { @include center; }
构建⼯具
-
背景:Vue等框架源代码⽆法直接运⾏,必须通过转换后才可以正常运行。
-
构建⼯具功能:源代码转换成可执⾏的JS、CSS、HTML代码。
- 代码转换:将TypeScript编译成JavaScript、将SCSS编译成CSS等。
- ⽂件优化:压缩JavaScript、CSS、HTML代码,压缩合并图⽚等。
- 代码分割:提取多个⻚⾯的公共代码,提取⾸屏不需要执⾏部分的代码让其异步加载。
- 模块合并:在采⽤模块化的项⽬⾥会有很多个模块和⽂件,需要通过构建功能将模块分类合并成⼀个⽂件。
- ⾃动刷新:监听本地源代码的变化,⾃动重新构建、刷新浏览器。
- 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
-
Npm Script
- Npm是在安装Node.js时附带的包管理器,Npm Script则是Npm内置的⼀个功能,是一个任务执⾏者,允许在package.json⽂件⾥⾯使⽤scripts字段定义任务:
{ "scripts": { "dev": "node dev.js", "build": "node build.js" } }
- 优点:内置,⽆须安装其他依赖。
- 缺点:功能太简单,虽然提供了pre和post两个钩⼦,但不能⽅便地管理多个任务之间的依赖。
-
Grunt
-
任务执⾏者。Grunt有⼤量现成的插件封装了常⻅的任务,也能管理任务之间的依赖关系,⾃动化地执⾏依赖的任务,每个任务的具体执⾏代码和依赖关系写在配置⽂件Gruntfile.js⾥
-
优点:
- 灵活,它只负责执⾏定义的任务
- ⼤量的可复⽤插件封装好了常⻅的构建任务
-
缺点:集成度不高,要写很多配置后才可以用,无法做到开箱即用
-
-
Gulp
-
基于流的⾃动化构建⼯具,提供插件去处理流,流可以在插件之间传递。除了可以管理和执⾏任务,还⽀持监听⽂件、读写⽂件。
- 通过gulp.task注册⼀个任务
- 通过gulp.run执⾏任务
- 通过gulp.watch监听⽂件的变化
- 通过gulp.src读取⽂件
-
优点:好⽤⼜不失灵活,既可以单独完成构建,也可以和其他⼯具搭配使⽤。
-
缺点:集成度不⾼,要写很多配置后才可以⽤,⽆法做到开箱即⽤。
-
-
Fis3
-
功能
- 读写⽂件:通过fis.match读⽂件,release配置⽂件的输出路径。
- 资源定位:解析⽂件之间的依赖关系和⽂件位置。
- ⽂件指纹:在通过useHash配置输出⽂件时为⽂件URL加上md5戳,来优化浏览器的缓存。
- ⽂件编译:通过parser配置⽂件解析器做⽂件转换,例如将ES6编译成ES5。
- 压缩资源:通过optimizer配置代码压缩⽅法。
-
优点:集成了各种Web开发所需的构建功能,配置简单、开箱即⽤。
-
缺点:⽬前官⽅已经不再更新和维护,不⽀持最新版本的Node.js。
-
-
Webpack
-
功能
- 打包模块化JS的⼯具,在Webpack⾥⼀切⽂件(如JS、CSS、SCSS、图⽚、模板)皆模块,能清晰地描述各个模块之间的依赖关系,通过Loader转换⽂件,通过Plugin注⼊钩⼦,最后输出由多个模块组合成的⽂件,这些文件能被浏览器直接使用。
-
优点:
- 专注于处理模块化的项⽬,能做到开箱即⽤、⼀步到位;
- 可通过Plugin扩展,完整好⽤⼜不失灵活;
- 使⽤场景不局限于Web开发;
- 社区庞⼤活跃,经常引⼊紧跟时代发展的新特性,能为⼤多数场景找到已有的开源扩展;
- 良好的开发体验。
module.exports = { entry: './app.js', output: { filename: 'bundle.js', } }
-
-
Rollup
-
和Webpack很类似但专注于ES6的模块打包⼯具。
-
优点:
- 对ES6源码进⾏Tree Shaking,去除那些已被定义但未被使⽤的代码,并进⾏作⽤域提升,以减⼩输出⽂件的⼤⼩,提升运⾏性能。
- 配置和使⽤更简单
- Rollup在⽤于打包JS库时⽐Webpack 更有优势,因为其打包出来的代码更⼩、更快。
-
缺点:生态还不完善,体验不如Webpack,功能不如Webpack完善
-
-
构建⼯具发展
- 1.原始阶段:直接引入
<script>
标签,可能导致全局变量污染、依赖难以维护、无代码压缩等优化流程。 - 2.任务运行阶段:
- Npm Script:内置,⽆须安装其他依赖,支持pre、post生命周期脚本。但功能太简单,但不能⽅便地管理多个任务之间的依赖。
- Grunt:配置文件驱动,但速度慢、插件复杂。
- Gulp:基于流和代码化任务,更快更灵活。
- 3.模块化打包阶段
- webpack:支持多种模块化,代码拆分,Loader/
- 1.原始阶段:直接引入
-
webpack使⽤
-
webpack安装
npm install webpack - g
,全局安装,可在任何地⽅共⽤⼀个Webpack可执⾏⽂件npm i - D webpack
,局部安装,可在项⽬根⽬录下对应的命令⾏⾥通过node_modules/.bin/webpack运⾏Webpack的可执⾏⽂件,也可在Npm Script⾥定义的任务会优先使⽤本项⽬下的Webpack.
{ "scripts": { "start": "webpack --config webpack.config.js", } }
- 推荐安装到项⽬,可防⽌不同的项⽬因依赖不同版本的Webpack⽽导致冲突。
-
webpack执行
- Webpack在执⾏构建时默认会从项⽬根⽬录下的webpack.config.js ⽂件中读取配置。由于Webpack构建运⾏在Node.js环境下,所以该⽂件最后需要通过CommonJS规范导出⼀个描述如何构建的Object对象。
// webpack.config.js const path = require('path'); module.exports = { entry: './main.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') } } // 在项⽬根⽬录下执⾏webpack命令运⾏Webpack构建,会发现⽬录下多出⼀个dist⽬录,⾥⾯有个bundle.js⽂件,bundle.js⽂件是⼀个可执⾏的JavaScript⽂件,它包含⻚⾯所依赖的两个模块main.js、show.js,以及内置的webpackBootstrap启动函数。这时⽤浏览器打开index.html⽹⻚,将会看到Hello,Webpack。
// index.html 导⼊webpack输出的js⽂件 <script src="./dist/bundle.js"></script>
- Webpack是⼀个打包模块化JavaScript的⼯具,它会从main.js出发,识别出源码中的模块化导⼊语句,递归地找出⼊⼝⽂件的所有依赖,将⼊⼝和其所有依赖打包到⼀个单独的⽂件中。从Webpack 2版本开始,Webpack已经内置了对ES6、CommonJS、AMD模块化语句的⽀持。
-
Loader
npm i -D style-loader css-loader
- Loader,具有⽂件转换功能,module.rules 数组配置了⼀组规则,告诉 Webpack 在遇到哪些⽂件时使⽤哪些 Loader 去加载和转换。
- use属性值需要是⼀个由Loader名称组成的数组,Loader的执⾏顺序是由后到前的;
- 每个 Loader 都可以通过 URL querystring 的⽅式传⼊参数,例如 css-loader?minimize中的minimize告诉css-loader要开启CSS压缩
- 除了在 webpack.config.js 配置⽂件中配置 Loader,还可以在源码中指定⽤什么Loader去处理⽂件。以加载CSS⽂件为例,
require('style-loader!css-loader?minimize!./main.css')
指定对./main.css这个⽂件先采⽤css-loader再采⽤style-loader进⾏转换:
// webpack.config.js const path = require('path'); module.exports = { // JS⼊⼝⽂件 entry: './main.js', output: { // 将所有依赖的模块合并输出到⼀个bundle.js⽂件中 filename: 'bundle.js', // 将输出⽂件都放到dist⽬录下 path: path.resolve(__dirname, 'dist') }, modules: { rules: [ { // ⽤正则表达式匹配要⽤该loader转换的css⽂件 test: /\.css$/, /* 在遇到以.css结尾的⽂件时,先使⽤css-loader读取CSS⽂件,再由style-loader将CSS的内容注⼊JavaScript⾥。运⾏webpack后,css被写在了js⾥,这是由style-loader实现的。style-loader的⼯作原理:将CSS的内容⽤JavaScript⾥的字符串存储起来,在⽹⻚执⾏JavaScript时通过DOM操作,动态地向HTML head标签⾥插⼊HTML style标签。 */ use: ['style-loader', 'css-loader?minimize'], } ] }
-
Plugin
npm i -D extract-text-webpack-plugin
- Plugin,⽤来扩展Webpack功能的,通过在构建流程⾥注⼊钩⼦实现。
- Webpack 通过 plugins 属性来配置需要使⽤的插件列表的。plugins属性是⼀个数组,⾥⾯的每⼀项都是插件的⼀个实例,在实例化⼀个组件时可以通过构造函数传⼊这个组件⽀持的配置属性。如,ExtractTextPlugin插件的作⽤是提取出JavaScript代码⾥的CSS到⼀个单独的⽂件中。对此可以通过插件的filename属性,告诉插件输出的CSS⽂件名称是通过[name]_[contenthash:8].css 字符串模板⽣成的,⾥⾯的[name]代表文件的名称,[contenthash:8]代表根据文件内容算出的 8 位 Hash 值。
- 示例:安装成功后重新执⾏构建,会发现 dist ⽬录下多出⼀个 main_1a87a56a.css ⽂件,bundle.js⽂件⾥也没有CSS代码了,再将该CSS⽂件引⼊index.html⾥就完成了。
// webpack.config.js const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { // JS⼊⼝⽂件 entry: './main.js', output: { // 将所有依赖的模块合并输出到⼀个bundle.js⽂件中 filename: 'bundle.js', // 将输出⽂件都放到dist⽬录下 path: path.resolve(__dirname, 'dist') }, modules: { rules: [ { // ⽤正则表达式匹配要⽤该loader转换的css⽂件 test: /\.css$/, loaders: ExtractTextPlugin.extract({ // 转换css⽂件需要使⽤的loader use: ['css-loader'] }), } ] }, plugins: [ new ExtractTextPlugin({ // 从.js⽂件中提取出.css⽂件的名称 filename: `[name]_[contenthash:8].css`, }) ] }
-
devServer
-
npm i -D webpack-dev-server
-
DevServer 会启动⼀个 HTTP 服务器⽤于服务⽹⻚请求,同时会帮助启动Webpack,并接收 Webpack 发出的⽂件更变信号,通过 WebSocket 协议⾃动刷新⽹⻚做到实时预览。
- 提供HTTP服务⽽不是使⽤本地⽂件预览;
- 监听⽂件的变化并⾃动刷新⽹⻚,做到实时预览;
- ⽀持Source Map,以⽅便调试。
-
-
-
总结
-
webpack配置参数:
- Entry:⼊⼝,Webpack执⾏构建的第⼀步将从Entry开始,可抽象成输⼊。
- Module:模块,在Webpack⾥⼀切皆模块,⼀个模块对应⼀个⽂件。Webpack会从配置的Entry开始递归找出所有依赖的模块。
- Chunk:代码块,⼀个Chunk由多个模块组合⽽成,⽤于代码合并与分割。
- Loader:模块转换器,⽤于将模块的原内容按照需求转换成新内容。
- Plugin:扩展插件,在Webpack构建流程中的特定时机注⼊扩展逻辑,来改变构建结果或做想要的事情。
-
webpack运⾏机制:
- Webpack在启动后会从Entry⾥配置的Module开始,递归解析Entry依赖的所有Module。每找到⼀个Module,就会根据配置的Loader去找出对应的转换规则,对Module进⾏转换后,再解析出当前Module依赖的Module。这些模块会以Entry为单位进⾏分组,⼀个Entry及其所有依赖的Module被分到⼀个组也就是⼀个Chunk。最后,Webpack会将所有Chunk转换成⽂件输出。在整个流程中,Webpack会在恰当的时机执⾏Plugin⾥
-
参考&感谢各路大神
- [深入浅出Webpack-吴浩麟]