Webpack原理(3) — 核心概念
by Teobler on 22 / 02 / 2020
views
Entry
我们先来看一张图
从这张图可以看到,最上面的文件就是我们整个app的入口,也是这个文件启动了我们整个app,这就是weback的入口,通常这个文件会依赖我们自己app的其他文件,其他文件又会依赖别的第三方库,这些依赖可能是js,也可能是css,当然右边也展示了我们也会依赖app里面的其他文件。
在webpack的config文件中,我们使用entry字段来设置这个入口:
module.exports = {
...
entry: "./main.js",
...
}
一句话来总结就是
Entry tells webpack WHAT(files) to load for the browser
Output
图片中入口文件下方的是入口文件的依赖,上方就是bundle之后的输出,同样的,我们在config文件中可以通过output这个字段来设置相应的配置项:
module.exports = {
...
output: {
path: "./dist",
filename: "./bundle.js",
},
...
}
这个配置就是在告诉webpack编译后的文件应该放在哪里,文件名应该叫什么,一句话总结
Output tells webpack WHERE and HOW to discribute bundles. It works with Entry.
Loaders & Rules
需要明白的是,loaders都是一些JS的modules,也就是说都是一些JS的方法(functions),他们以你app的module作为输入,返回一个修改后的状态,这些loaders会在webpack建立依赖图的时候对每一个文件进行相应的处理:
比如第一个ts-loader就是在说告诉webpack,任何时候你想要把一个ts文件放入依赖图中,就用ts-loader处理一次,处理过后这个文件就被编译成了js文件,当然,可能这个文件也有别的依赖,会按照相同的方式依次进行处理。
通常这个字段接收这些参数:
module.exports = {
rules: [
{
// 告诉编译器要编译哪些文件
test: regex,
// loader(s)
use: (Array | String | Function),
// 白名单
include: RegExp[],
// 黑名单
exclude: RegExp[],
// 告诉webpack这条规则是否在其他规则 之前 | 之后 运行
enforce: "pre" | "post"
},
]
}
Chaining Loaders
在上面的介绍中可以看到use
字段是可以传入一个数组的,比如["style", "css", "less"]
但是需要指出的是,这三个loaders是按照从右到左的的顺序来执行的,这个规则将使一个less文件编译成一个css文件,再由css编译成js文件,最后编译成一个能够在浏览器中运行的inline style
的js文件。
loaders tells webpack HOW to interpret and translate files. Transformed on a per-file basis before adding to dependency graph
Plugin
对于webpack来说,插件是什么:
- 一个对象,这个对象上有一个
apply
属性 - 允许你在编译的生命周期里做一些事情(hook)
- webpack有各种各样的内置插件
一个简单的例子,编译器用这个插件分发事件:
这个插件被作为一个实例传入了webpack,所以它可以hook进不同的事件中(这里的代码是webpack3的,因为这里只讲概念,所以问题不大)。
首先这个插件插入编译器后悔监听done
这个事件,这个事件会给这个插件传入一个参数,这里是一个state
然后插件在这个state的基础上做出一些反应和处理(这里只是在控制台里输出了一个字符),下面的也是同样的,不过这次这个事件换成了failed
可以看到插件的定义是这样的,所以它可以被实例化,于是在webpack的config文件中我们就会这样去使用它:
const BellOnBundlerErrorPlugin = require("bell-on-error");
const webpack = require("webpack");
module.exports = {
...
plugins: [
new BellOnBundlerErrorPlugin(),
new webpack.optimize.CommonsChunkPlugin("vendors"),
],
...
}
有意思的是,webpack的源代码有80%左右都是由plugins组成的,webpack是一个完全由事件驱动的体系结构,这也就可以使用插件迅速为webpack增添新的功能,并且不会破坏原有的功能,同样的也能够删除一些不必要的功能。
Adds additional functionality to Compilations(optimized bundled modules). More powerful more access to CompilerAPI. Does everything else you'd ever want to in webpack.
plugin与loader的最大不同就是loader是通过去访问一个个文件去做一些事情的,但是plugin可以访问webpack的事件生命周期和运行时,并且可以访问所有bundle文件。
示例
讲了这么多我们总得用概念做点什么
以下代码位于
feature/04010-composing-configs-webpack-merge
分支
读取命令行中的参数
我们可以将package.json
中的命令做一个修改,传入一个env变量,带有一个mode属性:
"script" {
"webpack": "webpack",
"dev": "npm run webpack -- --env.mode development"
}
然后在webpack.config.js
中我们可以这样获取到这个变量:
module.exports = env => {
console.log(env);
// { mode: "development" }
return {
mode: env.mode,
output: {
filename: "bundle.js",
}
}
}
加插件
我们这里以一个很通用并且很重要的插件html-webpack-plugin
为例
首先用yarn add html-webpack-plugin —dev
安装插件,然后在config文件中配置:
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = env => {
console.log(env);
// { mode: "development" }
return {
mode: env.mode,
output: {
filename: "bundle.js",
},
plugins: [new HtmlWebpackPlugin()],
}
}
之后再运行yarn dev
,你会发现bundle里面多了一个index.html
文件,文件中用script标签把已经打包好的JS代码进行了引入。
设置本地开发服务
首先安装server插件yarn add webpack-dev-server --dev
,然后修改package.json
:
"script" {
"webpack-dev-server": "webpack-dev-server",
"dev": "npm run webpack-dev-server -- --mode development"
}
之后当你运行yarn dev
的时候就会默认在本地8080端口启动一个server,在浏览器访问这个端口你就可以看见刚刚生成的html文件了,而且dev server默认开启了watch模式,可以实时更新代码到server上。
其实大家也能够猜到这个”插件“其实就是启动了一个Express的server,然后webpack在打包的时候直接将生成的文件加载进内存,通过server直接显示在浏览器里。
为不同的环境设置不同的config文件
我们可以像代码库中一样通过简单的配置使得webpack在得到不同参数的情况下去require不同的webpack config文件,以达到区分dev环境和prod环境的目的:
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpackMerge = require("webpack-merge");
const modeConfig = env => require(`./build-utils/webpack.${env}`)(env);
module.exports = ({ mode, presets } = { mode: "production", presets: [] }) => {
return webpackMerge(
{
mode,
output: {
filename: "bundle.js"
},
plugins: [new HtmlWebpackPlugin(), new webpack.ProgressPlugin()]
},
modeConfig(mode)
);
};
但是实际的项目往往要复杂得多,一个大型项目往往不止有两个环境,通常来说development mode
下应该有CI Dev QA UAT四个环境,production mode
对应Prod环境。
由于在开发时不同的环境又对应不同的config,比如在Dev坏境应该去请求后端的Dev坏境,而不应该去请求QA坏境,这时我们又会引入不同的config文件去实现这一点。
首先安装config
这个包,,然后在根目录下新建一个config目录,新建你需要的config文件,这些文件都是JSON格式的,记得建一个default.json
,当其找不到对应的坏境时就会默认读取default文件,文件可以长这个样子:
// default.json
{
"API_BASE_URL": "https://dev.your-project.com",
"AUTH_URL": "https://dev.your-project.com/auth",
"NODE_ENV": "dev"
}
//qa.json
{
"API_BASE_URL": "https://qa.your-project.com",
"AUTH_URL": "https://qa.your-project.com/auth",
"NODE_ENV": "qa"
}
然后在运行启动命令的时候加上NODE_ENV
这个参数,这样你在webpack的config文件中就可以通过process.env.NODE_ENV
去获取这个参数,并且在config文件中可以将之前的config文件require进来在代码中进行使用:
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpackMerge = require("webpack-merge");
const modeConfig = env => require(`./build-utils/webpack.${env}`)(env);
const envConfig = JSON.stringify(require("config"));
module.exports = ({ mode, presets } = { mode: "production", presets: [] }) => {
return webpackMerge(
{
mode,
output: {
filename: "bundle.js"
},
plugins: [
new HtmlWebpackPlugin(),
new webpack.ProgressPlugin(),
new webpack.DefinePlugin({ CONFIG: envConfig }),
]
},
modeConfig(mode)
);
};
// axios.js
const client = axios.create({
baseURL: CONFIG.API_BASE_URL,
timeout: 10000,
});