本文为做者第二次专门对 Webpack 的知识点进行深刻和实践,根据理解和实践的结果进行总结的;javascript
文章内容参考书籍《深刻浅出 Webpack》,由于该书籍基于 Webpack 3.4.0 版本,本文的实践基于 Webpack 4.28.2 版本,因此也踩了很多因为模块版本问题出现的坑,已经汇总到第 6 章节 踩坑汇总,你们记得避免踩坑;也印证了那句哲理:纸上得来终觉浅,绝知此事要躬行 ...css
博客 github地址为:github.com/fengshi123/… ,汇总了做者的全部博客,也欢迎关注及 star ~html
本文实践 demo 的 github地址java
构建工具就是将源代码转换成可执行的 JavaScript、CSS、HTML 代码,包括如下内容:node
代码转换:将 TypeScript 编译成 JavaScript、将 SCSS 编译成 CSS 等;webpack
文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等;git
代码分割:提取多个页面的公共代码,提取首屏不须要执行部分的代码,让其异步加载;github
模块合并:在采用模块化的项目里会有不少个模块和文件,须要经过构建功能将模块分类合并成一个文件;web
自动刷新:监听本地源代码的变化,自动从新构建、刷新浏览器;npm
代码校验:在代码被提交到仓库前须要校验代码是否符合规范,以及单元测试是否经过;
自动发布:更新代码后,自动构建出线上发布代码并传输给发布系统;
Webpack 有如下几个核心概念:
Entry :入口,Webpack 执行构建的第一步将从 entry 开始,可抽象成输入;
Module:模块,配置处理模块的规则;在 Webpack 里一切皆模块,一个模块对应一个文件;Webpack 会从配置的 Entry 开始递归找出全部依赖的模块;
Loader:模块转换器,用于将模块的原内容按照需求转换成新内容;
Resolve:配置寻找模块的规则;
Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播对应的事件,插件能够监听这些事情的发生,在特定的时机作对应的事情;
Output:输出结果,在 Webpack 通过一系列处理并得出最终想要的代码后输出结果;
Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割;
(1)初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
(2)开始编译:用上一步获得的参数初始化 Compiler 对象,加载全部配置的插件,经过执行对象的 run 方法开始执行编译;
(3)肯定入口:根据配置中的 entry 找出全部入口文件;
(4)编译模块:从入口文件出发,调用全部配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到全部入口依赖的文件都通过了本步骤的处理;
(5)完成模块编译:在通过第 4 步使用 Loader 翻译完全部模块后,获得了每一个模块被翻译后的最终内容及它们之间的依赖关系;
(6)输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再将每一个 Chunk 转换成一个单独的文件加入输出列表中,这是能够修改输出内容的最后机会;
(7)输出完成:在肯定好输出内容后,根据配置肯定输出的路径和文件名,将文件的内容写入文件系统中;
在以上过程当中,Webpack 会在特定的时间点广播特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,而且插件能够调用 Webpack 提供的 API 改变 Webpack 的运行结果;
一、新建 Web 项目
新建一个目录,再进入项目根目录执行 npm init 来初始化最简单的采用了模块化开发的项目;最终生成 package.json 文件;
$ npm init
复制代码
二、安装 Webpack 到本项目
(1)查看 Webpack 版本
运行如下命令能够查看 Webpack 的版本号
$ npm view webpack versions
复制代码
(2)安装 Webpack
能够选择(1)步骤罗列获得的 Webpack 版本号,也能够安装最新稳定版、最新体验版本,相关命令以下所示,我选择安装 4.28.2 版本(没有为何,就想装个 4.x 的版本);
// 安装指定版本
npm i -D webpack@4.28.2
// 安装最新稳定版
npm i -D webpack
// 安装最新体验版本
npm i -D webpack@beta
复制代码
(3)安装 Webpack 脚手架
须要安装 Webpack 脚手架,才能在命令窗口执行 Webpack 命令,运行如下命令安装 Webpack 脚手架;
$ npm i -D webpack-cli
复制代码
三、使用 Webpack
使用 Webpack 构建一个采用 CommonJS 模块化编写的项目;
(1)新建页面入口文件 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Webpack</title>
</head>
<body>
<!--导入 Webpack 输出的 JavaScript 文件-->
<script src="./dist/bundle.js"></script>
</body>
</html>
复制代码
(2)新建须要用到的 JS 文件
show.js 文件
// 操做 DOM 元素,把 content 显示到网页上
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
// 经过 CommonJS 规范导出 show 函数
module.exports = show;
复制代码
main.js 文件
// 经过 CommonJS 规范导入 show 函数
const show = require('./show.js');
// 执行 show 函数
show('Webpack');
复制代码
(3)新建 Webpack 配置文件 webpack.config.js
const path = require('path');
module.exports = {
// JavaScript 执行入口文件
entry: './main.js',
output: {
// 把全部依赖的模块合并输出到一个 bundle.js 文件
filename: 'bundle.js',
// 输出文件都放到 dist 目录下
path: path.resolve(__dirname, './dist'),
}
};
复制代码
(4)执行 webpack 命令进行构建
在 package.json 文件中配置编译命令,以下所示:
"scripts": {
"build": "webpack --config webpack.config.js",
},
复制代码
执行如下命令进行项目的 Webpack 编译,成功后会在项目根目录下生成编译目录 dist ;
$ npm run build
复制代码
(5)运行 index.html
编译成功后,咱们用浏览器打开 index.html 文件,能看到页面成功显示 “Hello Webpack”;
本节经过为以前的例子添加样式,来尝试使用 Loader;
(1)新建样式文件 main.css
#app{
text-align: center;
color:'#999';
}
复制代码
(2)将 main.css 文件引入入口文件 main.js 中,以下所示:
// 经过 CommonJS 规范导入 CSS 模块
require('./main.css');
// 经过 CommonJS 规范导入 show 函数
const show = require('./show.js');
// 执行 show 函数
show('Webpack');
复制代码
(3)Loader 配置
以上修改后去执行 Webpack 构建是会报错的,由于 Webpack 不原生支持解析 CSS 文件。要支持非 JavaScript 类型的文件,须要使用 Webpack 的 Loader 机制;
(3.1)运行如下命令,安装 style-loader 和 css-loader,其中:
$ npm i -D style-loader css-loader
复制代码
(3.2)进行如下配置
module:{
rules:[
{
// 用正则去匹配要用该 loader 转换的 CSS 文件
test:/\.css$/,
use:['style-loader','css-loader']
}
]
}
复制代码
(4)查看结果
编译后,刷新 index.html ,查看刚刚的样式 loader 已经起做用;
(1)安装样式提取插件 extract-text-webpack-plugin
$ npm i -D extract-text-webpack-plugin@next
复制代码
(2)plugin 文件配置以下
module:{
rules:[
{
// 用正则去匹配要用该 loader 转换的 CSS 文件
test:/\.css$/,
use:ExtractTextPlugin.extract({
use:['css-loader']
}),
}
]
},
plugins:[
new ExtractTextPlugin({
// 从 .js 文件中提取出来的 .css 文件的名称
filename:`[name]_[hash:8].css`
}),
]
复制代码
(3)查看结果
经过以上配置后,执行 Webapack 的执行命令,发如今 dist 目录下,生成对应的 css 文件;存在的坑点:
(1)执行如下命令安装 webpack-dev-server
$ npm i -D webpack-dev-server
复制代码
在 package.json 中配置启动命令
"scripts": {
"build": "webpack --config webpack.config.js",
"dev": "webpack-dev-server",
},
复制代码
运行命令后,就能够启动 HTTP 服务
$ npm run dev
复制代码
启动结果以下所示,咱们能够经过 http://localhost:8080/ 访问咱们的 index.html 的demo
(2)实时预览
咱们在运行命令后面添加参数 --watch 实现实时预览,配置以下所示:
"scripts": {
"dev": "webpack-dev-server --watch"
},
复制代码
而后咱们修改 main.js 的传入参数,发现并不能实时预览,也没有报错!!! why?
踩坑:
在 index.html 中须要将 js 的路径修改成:
<script src="bundle.js"></script>
复制代码
而不能是以前的(由于这个是编译生成的,并非经过 devServer 生成放在内存的)
<script src="./dist/bundle.js"></script>
复制代码
(3)模块热替换
能够经过配置 -- hot 进行模块热替换;
关于优化的实践以前有进行过实践了,这里再也不累述,感兴趣的童鞋能够查看做者写的另外一篇文章《Vue项目Webpack优化实践,构建效率提升50% 》
(1)Loader 为模块转换器,用于将模块的原内容按照需求转换成新内容;
(2)Loader 的职责是单一的,只须要完成一种转换,遵照单一职责原则;
(3)Webpack 为 Loader 提供了一系列 API 供 Loader 调用,例如:
手写一个 loader 源码,其功能是将 /hello/gi 转换成 HELLO,固然这个 loader 其实没啥实际意义,纯碎是为了写 loader 而写 loader;固然若是你实际业务有须要编写 loader 需求,那就要反思这个业务的合理性,由于庞大的社区,通常合理的需求都能找到对应的 loader。
(1)源码编写
在原有的项目底下,新建目录 custom-loader 做为咱们编写 loader 的名称,执行 npm init 命令,新建一个模块化项目,而后新建 index.js 文件,相关源码以下:
function convert(source){
return source && source.replace(/hello/gi,'HELLO');
}
module.exports = function(content){
return convert(content);
}
复制代码
(2)Npm link 模块注册
正常咱们安装 Loader 是从 Npm 公有仓库安装,也即将 Loader 发布到 Npm 仓库,而后再安装到本地使用;可是咱们可使用 Npm link 作到在不发布模块的状况下,将本地的一个正在开发的模块的源码连接到项目的 node_modules 目录下,让项目能够直接使用本地的 Npm 模块;
在 custom-loader 目录底下,运行如下命令,将本地模块注册到全局:
$ npm link
复制代码
成功结果以下:
而后在项目根目录执行如下命令,将注册到全局的本地 Npm 模块连接到项目的 node_modules 下:
$ npm link custom-loader
复制代码
成功结果以下,而且在 node_modules 目录下能查找到对应的 loader;
该配置跟第一章节的 Webpack 配置并无任何区别,这里再也不详述,配置参考以下:
module:{
rules:[
{
test:/\.js/,
use:['custom-loader'],
include:path.resolve(__dirname,'show')
}
]
}
复制代码
执行运行 or 编译命令,就能看到咱们的 loader 起做用了。
Webpack 就像一条生产线,要通过一系列处理流程后才能将源文件转换成输出结果,这条生产线上的每一个处理流程的职责都是单一的,多个流程之间存在依赖关系,只有在完成当前处理后才能提交给下一个流程去处理。插件就像生产线中的某个功能,在特定的时机对生产线上的资源进行处理。
Webpack 经过 Tapable 来组织这条复杂的生产线。 Webpack 在运行过程当中会广播事件,插件只须要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运做。 Webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。
手写一个 plugin 源码,其功能是在 Webpack 编译成功或者失败时输出提示;固然这个 plugin 其实没啥实际意义,纯碎是为了写 plugin 而写 plugin;固然若是你实际业务有须要编写 plugin 需求,那就要反思这个业务的合理性,由于庞大的社区,通常合理的需求都能找到对应的 plugin。
(1)源码编写
在原有的项目底下,新建目录 custom-plugin 做为咱们编写 plugin 的名称,执行 npm init 命令,新建一个模块化项目,而后新建 index.js 文件,相关源码以下:
class CustomPlugin{
constructor(doneCallback, failCallback){
// 保存在建立插件实例时传入的回调函数
this.doneCallback = doneCallback;
this.failCallback = failCallback;
}
apply(compiler){
// 成功完成一次完整的编译和输出流程时,会触发 done 事件
compiler.plugin('done',(stats)=>{
this.doneCallback(stats);
})
// 在编译和输出的流程中遇到异常时,会触发 failed 事件
compiler.plugin('failed',(err)=>{
this.failCallback(err);
})
}
}
module.exports = CustomPlugin;
复制代码
(2)Npm link 模块注册
跟 Loader 注册同样,咱们使用 npm link 进行注册;
在 custom-plugin 目录底下,运行如下命令,将本地模块注册到全局:
$ npm link
复制代码
而后在项目根目录执行如下命令,将注册到全局的本地 Npm 模块连接到项目的 node_modules 下:
$ npm link custom-plugin
复制代码
若是一切顺利,能够在 node_modules 目录下能查找到对应的 plugin;
该配置跟第一章节的 Webpack 配置并无任何区别,这里再也不详述,配置参考以下:
plugins:[
new CustomPlugin(
stats => {console.info('编译成功!')},
err => {console.error('编译失败!')}
),
],
复制代码
执行运行 or 编译命令,就能看到咱们的 plugin 起做用了。
一、css-loader 如下配置
rules:[
{
// 用正则去匹配要用该 loader 转换的 CSS 文件
test:/\.css$/,
use:['style-loader','css-loader?minimize']
}
]
复制代码
报如下错误:
- options has an unknown property 'minimize'. These properties are valid:
object { url?, import?, modules?, sourceMap?, importLoaders?, localsConventio n?, onlyLocals?, esModule? }
复制代码
缘由:
minimize 属性在新版本已经被移除,
解决:
先去掉 minimize 选项;
二、ExtractTextPlugin 编译如下错误:
缘由:
extract-text-webpack-plugin 版本号问题
解决:
从新安装 extract-text-webpack-plugin
$ npm i -D extract-text-webpack-plugin@next
复制代码
三、修复第2个坑以后,ExtractTextPlugin 编译继续报如下错误:
缘由:
不存在 contenthash 这个变量
解决:
更改 extract-text-webpack-plugin 的配置:
plugins:[
new ExtractTextPlugin({
// 从 .js 文件中提取出来的 .css 文件的名称
filename:`[name]_[hash:8].css`
}),
]
复制代码
四、添加 HappyPack 后,编译 CSS 文件时报如下错误:
缘由:
css-loader 版本的问题
解决:
从新安装 css-loader@3.2.0
本文主要基于 Webpack 的做用、核心概念、流程,Webpack 的基础配置,Webpack 优化,编写 Loader,编写 Plugin ,从理论到实践,从基础到较难,对 Webpack 进行总结掌握,但愿对你也有帮助。仍是那句话:纸上得来终觉浅,绝知此事要躬行 ...,若是你没有手敲过,必定要多动动手 !
博客 github地址为:github.com/fengshi123/… ,汇总了做者的全部博客,也欢迎关注及 star ~
本文实践 demo 的 github地址