持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情
(一)webpack的作用
分析你的项目结构,找到 JavaScript
模块以及其它的一些浏览器不能直接运行的拓展语言(SCSS
,TypeScript
等),并将其打包为合适的格式以供浏览器使用。
构建就是把源代码转换成发布到线上的可执行 JavaScript
、CSS
、HTML
代码,包括如下内容。
- 代码转换:
TypeScript
编译成JavaScript
、SCSS
编译成CSS
等loader
。 - 文件优化:压缩
JavaScript
、CSS
、HTML
代码,压缩合并图片等。 - 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
- 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
- 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
- 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
- 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
构建其实是工程化、自动化思想在前端开发中的体现,把一系列流程用代码去实现,让代码自动化地执行这一系列复杂的流程。 构建给前端开发注入了更大的活力,解放了我们的生产力。
(二)webpack的loader
loader
从本质上来说其实就是一个 node
模块
单一原则:每一个人 loader
只做一件事,因为东西越小越好组合,越好维护 从右到左,链式执行,上一个 loader
的处理结果给下一个接着处理。
模块化: 每一个模块都是独立的,不依赖任何东西,一定的输入就有一定的输出
无状态
链式调用:webpack
会按照顺序链式调用每一个 loader
。
统一原则:遵循 webpack
制定的设计规则和结构,输入与输出均为字符串,各个 loader
完全独立,即插即用。
1.1 在项目中安装了 webpack
和 webpack-cli
1.2 新建一个 webpack.config.js
文件
const path = require('path')
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [{
test: /\.js$/,
loader: path.resolve(__dirname, 'src', 'loaders', 'log-loader')// 这个就是我们自己的写的loader所在的目录
}]
}
}
复制代码
1.3 分别建几个目录 src/base.js
,src/index.js
,src/loaders/log-loader
module.exports = 'sunseekers' // base.js
const name = require('./base') // index.js
// loaders/log-loader
module.exports = function (sourse) {
console.log('在webpack里面配置了一个rules,现在所有js文件都会进过一个loader文件,进行处理,这里进过的是我们自己建的一个loader文件,sourse是文件的原内容。这个日志会在build的时候看到这一句话');
return sourse
}
复制代码
1.4 配置 webpack
的命令,在 package.json
{
"name": "webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --mode production"// 主要是这一句话
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack-cli": "^3.3.11",
"webpack": "^4.3.0"
}
}
复制代码
关于 loader
具体的写法的使用可以查看文档,或者通过 console.log
来查看日志 loader API
file-loader
是解析图片地址,把图片从源位置拷贝到目标位置并且修改原引用地址
url-loader
可以在文件比较小的时候,直接变成base64字符串内嵌到页面中
(三)webpack的plugin
插件向第三方开发者提供了 webpack
引擎中完整的能力。使用阶段式的构建回调,开发者可以引入它们自己的行为到 webpack
构建流程中。创建插件比创建 loader
更加高级,因为你将需要理解一些 webpack
底层的内部特性来做相应的钩子
webpack
基础配置无法满足需求
插件几乎能够任意更改 webpack
编译结果这里才是我们写插件最重要的原因,输出我们想要的结果
webpack
内部也是通过大量内部插件实现的
plugin
的写法比 loader
要难很多,需要我们对 webpack
的生命周期有一定的了解,了解他内置的一些属性和方法。比如 compiler
对象代表了完整的 webpack
环境配置,只有构建一次,最重要的。compilation
代表每一次资源版本的构建每一次我们修改代码更新的时候都会构建一次
3.1 在配置文件中使用自己写的插件 webpack.config.js
const path = require('path')
const FilePlugin = require('./src/plugins/filename') // 自己插件地址
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
plugins: [
new FilePlugin({
filename: "file.list.md"
})
]
}
}
复制代码
3.2 开始写插件
a. 插件是一 JavaScript
命名函数 b. 在函数身上定义一个 apply
方法,处理内部实例自身的时间钩子函数 c. 功能完成之后调用 webpack
提供的回调
// 生成一个新的md文档,里面放着所有的文件名
class FilesPlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
// compiler 要启动一次新的编译
// 你在哪监听这个事件
compiler.hooks.emit.tapAsync("EmitPlugin", (compilation, callback) => { // 这里就是我们需要了解 webpack 的生命周期和内部一些api的执行
console.log('看看静态资源是什么格式 :',compilation.assets);
let content = `## 文件列表\n\n`
const assets = compilation.assets
for (let path in assets) {
content += `-- ${path}\n\n`
}
compilation.assets[this.options.filename || 'filelist.md'] = {
source() {
return content
},
size() {
return Buffer.byteLength(content)
}
}
callback()
})
}
}
module.exports = FilesPlugin
复制代码
个人见解
上面都是一些很简单的例子,只说告诉你怎么写,我们用的哪些插件远远比这些都要复杂的多,有些插件需要我们对 ast
有一定的了解。看了一段时间的 webpack
,我发现,要学习他们底层的源码或者知道一个东西是怎么写的,首先你要对他的流程特别清楚,然后按部就班的写。我们平时调用的 js
的 api
也是一样的。你知道 api
怎么用,有哪些参数,返回什么,这个函数做了一些什么。有了这样的理解思路会好很多,写起来就没有那么的难了。