webpack 介绍
webpack 可以不做任何配置,直接执行 webpack 命令就可以打包我们代码,也可以手动配置一些 webpack 来打包我们的代码, webpack 配置只需要在项目根目录下新建一个
webpack.config.js文件,然后导出一个对象或者函数都可以,如果导出为一个函数那么这个函数接受两个参数,第一个参数是环境对象(environment),第二个参数是一个 map 对象(argv)这个对象描述了传递给 webpack 的选项,并且具有 output-filename 和 optimize-minimize 等 key。
来看一下这两个参数:
// 在 webpack.config.js 中输入以下代码
module.exports = function(env, argv) {
console.log(env, argv)
}
// 在控制台执行
webpack --env=chengyuming
- 让
webpack正常启动需要最少需要两个npm包,webpack、webpack-cli
一、entry
webpack 是采用模块化的思想,所有的文件或者配置都是一个个的模块, 同时所有模块联系在一起,可以理解为就是一个简单的树状结构,那么最顶层的入口是什么呢?答案就是
entry, 所以,webpack在执行构建的时候, 第一步就是找到入口,从入口开始,寻找,遍历,递归解析出所有入口依赖的模块。
entry用法如下
- 当然也可以采用动态配置
entry, 采用箭头函数动态返回。
- 关于
entry, 我们要记住, 他有多种配置类型, 而且可以动态配置, 还可以为入口设置别名呐。 - 也可以引用定义三方模块的打包,配置如下
entry: {
app: path.resolve('./src/main.js'),
// 配置公共文件
vender: ['./src/jquery.js']
},
二、output
output是一个对象,里面包含一些列输出配置项- 常用的语法如下:
output: {
// 打包后的位置,必须是绝对地址
path: path.resolve('./dist'),
// 打包后的文件名,[name] 对应 entry 的名字
filename: 'js/[name].[chunkHash].js',
// 打包后静态资源的位置,cdn 的配置
publicPath: '.'
},
三、mode
webpack有三种mode:development、production、none
- development:会将
process.env.NODE_ENV的值设置为development,启用NamedChunksPlugin和NamedModulesPlugin。代码不压缩 - production:会将
DefinePlugin中process.env.NODE_ENV的值设置为production。启用FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin,OccurrenceOrderPlugin,SideEffectsFlagPlugin和TerserPlugin。 - none:退出任何默认优化选项
- 如果没有设置,
webpack会把mode默认设置为production,并发出警告。 - 我们可以通过控制台中输入
--mode=development来设置webpack的模式,也可以在webpack.config.js中设置mode: development - 设置
NODE_ENV并不会自动地设置mode。 - 如下,一个简单的 web 打包机就配置成功了,编译、优化就是
modules、plugins、optimization配置的事情,配置规则在基础篇已经介绍过了
const path = require('path');
module.exports = function (env, argv) {
return {
mode: argv.mode || 'development',
entry: path.resolve('./src/main'),
output: {
path: path.resolve('./dist'),
filename: '[name].[hash].js',
},
};
};
四、module
配置 loader
配置
loader就是配置一堆规则,所以要放在module.rules中,先看一段代码
module: {
rules: [{
test: /\.js$/,
include: path.resolve(__dirname, 'src'),
// use可以是普通字符串数组,也可以是对象数组
use: ['babel-loader?cacheDirectory'],
use: [{
loader: 'babel-loader',
options: {
cacheDirectory: true, //
},
enforce: 'post'
}]
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
exclude: path.resolve(__dirname, 'node_modules')
},
{
// 对非文本文件采用 file-loader 加载
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: ['file-loader'],
},
//配置更多的其他loader
]
}
::: tip 属性说明
test/include/exclude: 表示匹配到loader的文件或者文件范围;use: 表示使用什么loader, 它可以是一个字符串数组, 也可以是对象数组, 那多个loader时, 执行顺序是从右向左, 当然, 也可以使用enforce强制让某个loader的执行顺序放到最前面或者最后面;cacheDirectory: 表示传给babel-loader的参数, 用于缓存babel的编译结果, 加快编译速度。enforce:post表示将改loader的执行顺序放到最前面,pre则相反.- 多个
loader时处理顺序:从后到前,即先交给sass-loader处理, 再将结果交给css-loader, 最后交给style-loader:::
配置 noParse
::: tip noParse
noParse 可以用于让 webpack 忽略哪些没有采用模块化的文件, 不对这些文件进行编译处理, 这样做可以提高构建性能, 因为例如一些库: 如 jquery 本身是没有采用模块化标注的, 让 webpack 去解析这些文件即耗时, 也没什么意义。
:::
module: {
rules: [],
noParse: /jquery/,
noParse: content => {
return /jquery/.test(content);
}
}
::: tip 说明:
noParse的值可以是正则表达式, 也可以是一个函数;- 被忽略的文件里不应该包含
import、require、define等模块化语句, 不然会导致在构建出的代码中包含无法在浏览器环境下执行的模块化语句 :::
配置 parser
::: tip parser
因为 Webpack 是以模块化的 JavaScript 文件为入口的,所以内置了对模块化 JavaScript 的解析功能,支持 AMD, CommonJS、SystemJS、ES6。parser 属性可以更细粒度地配置哪些模块语法被解析、哪些不被解析。同 noParse 配置项的区别在于,parser 可以精确到语法层面,而 noParse只能控制哪些文件不被解析。
:::
module: {
rules: [
test: /\.js$/,
use: ['babel-loader'],
parse: [
amd: false, // 禁用AMD
commonjs: false, // 禁用 CommonJS
system: false, // 禁用 SystemJS
harmony: false, // 禁用 ES6 import/export
requireinclude: false, // 禁用require.include
requireEnsure: false, // 禁用require.ensure
requireContext: false, // 禁用require.context
browserify: false, // 禁用
browserify requireJs: false, // 禁用 requirejs: false, //禁用requirejs
]
]
}
::: tip 说明:
parse和noParse同级的属性,当然也可以嵌套到rules,表示针对与某个loader应用到该属性的规则。- 目前只要明白
parse属性,是用于声明哪些模块语法被解析,哪些不被解析即可。 :::
单个规则配置多个 loader, 语法需要使用 use, 如图
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// publicPath: '../',
reloadAll: true
}
},
'css-loader',
'postcss-loader',
'sass-loader'
]
},
五、resolve
::: tip resolve
resolve 配置 webpack 去寻找模块对应的文件,我们平常通过 import 导入的模块,resolve 可以告诉 webpack 如何去解析导入的模块
:::
1. alias:配置路径别名
resolve: {
alias: {
'@': path.join(__dirname, '..', 'src')
}
}
2. extensions:用于配置模块文件的后缀列表
用来配置文件可以不写后缀名
resolve: {
extension: ['.js', '.json', '.vue', '.jsx'];
}
3. modules
::: tip
resolve.modules 配置的 webpack 默认会去 node_modules 目录下寻找,假如项目中遇到一些模块大量依赖和导入由于 其他模块 的位置不定, 针对不同的文件都要计算被导入的模块文件的相对路径,这个路径 有时会很长,例如:就像 import ······ '../../../components/module-a.js',这时可以利用 modules 配置项优化。假如那些被大量导入的模块都在 ./src/components 目录下,则将 modules 配置成这样
:::
resolve: {
modules: ['./src/components', 'node_modules'];
}
// 然后引入模块就会去这两个目录中寻找
import moduleA from 'module-a'; // module-a.js 在 ./src/components 目录下
::: warning
此时我们就可以简单的通过 import ······ 'module-a' 导入
注意: modules 和 alisa 的区别:modules 是用来配置一些公共模块,这些公共模块和 node_modules 类似,配置以后,我们就可以直接引用模块,前面不需要再加路径,而 alias 作用是配置路径别名,目的是可以让路径简化。两者是不一样的。
除此之外,还有:
descriptionFiles:配置描述第三方模块的文件名称:默认是package.jsonenforceExtension:配置后缀名是否必须加上 :::
六、plugin
::: tip plugin
plugins其实包括webpack本身自带的插件,也有开源的其他插件,都可以使用,它的作用就是解决loader之外的其他任何相关构建的事情。plugin的值是一个数组,可以传入多个插件实例,用法是直接new一个插件然后传入相应的配置即可plugin如何配置并不是难点,难点是我们需要清楚常用的一些插件分别解决了什么样的问题,以及这些插件的配置项 :::
plugins: [
// 处理vue
new VueLoaderPlugin(),
// 分离css,只需要传入相应的配置即可
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[id].[hash].css',
}),
];
七、optimization
::: tip optimization
optimization 很明显是优化代码的,比如说我们的压缩代码,就是在这里面配置,从 webpack4 开始,如果 mode 是 production 模式会默认压缩代码,具体可查看官网文档
:::
optimization: {
// 简单配置压缩代码
minimize: true,
// 分离代码
splitChunks: {
chunk: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // 只打包初始时依赖的第三方
},
vue: {
name: 'vue',
test: /[\\/]node_modules[\\/]vue[\\/]/,
priority: 11,
},
vueRouter: {
name: 'vue-router',
test: /[\\/]node_modules[\\/]vue-router[\\/]/,
priority: 12,
}
}
}
}
八、devServer
::: tip devServer
devServer 主要用于本地开发的时候,配置本地服务的各种特性,常用的配置如下
:::
- hot: true/false // 是否开启模块热替换
- inline: true/false; // 是否开启实时刷新, 即代码更改以后, 浏览器自动刷新
- contentBase // 用于配置本地服务的文件根目录
- header // 设置请求头
- host // 设置域名
- port // 设置端口
- allowedHosts: [] // 只有请求的域名在该属性所配置的范围内, 才可以访问。
- https: true/false; // 使用使用 https 服务, 默认为 false
- compress: true/false; // 是否启用 Gzip 压缩, 默认为 false.
- open //是否开启新窗口
- devtool : 'source-map' // 配置 webpack 是否生成 source Map, 以方便调试。
- watch: true // 默认为 true, 表示是否监听文件修改以后, 自动编译。
附上一个简单的配置
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = function (env, argv) {
return {
mode: argv.mode || 'development',
entry: path.resolve('./src/main'),
output: {
path: path.resolve('./dist'),
filename: 'js/[name].[chunkHash].js',
publicPath: '.',
},
module: {
rules: [
// 使用MiniCssExtractPlugin插件后的配置
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
reloadAll: true,
},
},
'css-loader',
],
},
],
},
plugins: [
// 配置HTML
new HTMLWebpackPlugin({
// 源文件模板
template: path.resolve('./public/index.html'),
// 输出文件
filename: 'index.html',
title: 'fecym',
inject: true,
hash: true,
showErrors: true,
}),
// 分离css
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[id].[hash].css',
}),
],
optimization: {
minimize: true,
runtimeChunk: {
name: entryPoint => `runtime~${entryPoint.name}`,
},
},
};
};
webpack 一些概念
一、module,chunk 和 bundle
- 对于同逻辑代码,当我们手写下一个个文件,他们都是
module; - 当我们写的
module源文件传到webpack进行打包时,webpack会根据文件引用关系生成chunk文件,webpack会对这个chunk文件进行一些操作; webpack处理好chunk文件后,最后会输出bundle文件,这个bundle文件包含了经过加载和编译的最终源文件,所以它可以直接在浏览器中运行。 总结:module、chunk、bundle其实就是同一份逻辑代码在不同转换场景下取了不同的名字:我们直接写出来的是module,webpack处理时是chunk,最后在浏览器中可以直接运行的是bundle
二、filename 和 chunkFilename
filename是一个很常见的配置,就是对应于entry里面的输入文件,经过webpack打包后输出文件的文件名;chunkFilename指未被列在entry中,却又需要被打包出来的chunk文件的名称。一般来说:这个chunk文件指的是要懒加载的代码(可以在output里面配置)- 总结:
filename指列在entry中,打包后输出的文件的名称;chunkFilename指未被列在entry中,却又需要被打包出来的文件的名称
三、预请求和预加载
preload chunk会在父chunk加载时,以并行方式开始加载。prefetch chunk会在父chunk加载结束后开始加载。preload chunk具有中等优先级,并立即下载。prefetch chunk在浏览器闲置时下载。preload chunk会在父chunk中立即请求,用于当下时刻。prefetch chunk会用于未来的某个时刻。- 总结:
webpackChunkName是为预加载的文件取别名。webpackPrefetch会在浏览器闲置时下载文件,webpackPreload会在父chunk加载时并行下载文件。
四、hash、chunkHash 和 contentHash
哈希一般是结合
CDN缓存来使用的。 如果文件内容改变的话, 那么对应文件哈希值也会改变, 对应的HTML引用的URL地址也会改变, 触发CDN服务器从源服务器上拉取对应数据, 进而更新本地缓存。
- hash: 计算是跟整个项目的构建相关,所有的文件都用一个
hash,一个文件发生改变,其他都会改变,hash值是一个。 - chunkHash:跟
hash差不多。区别在与chunkHash所有公共库的代码文件都用一个hash,公共库的代码用一个hash,当我们更新普通模块内容的时候,其他模块的hash值发生改变,但是公共模块的hash不会受到影响
// 简单配置如下
{
entry: {
app: path.resolve('./src/main.js'),
// 配置公共文件
vender: ['./src/jquery.js']
},
output: {
path: path.resolve('./love'),
filename: 'js/[name].[chunkHash].js'
},
}
-
contenthash:所有的文件的
hash都是不一样的,哪个文件被改变,哪个文件的hash发生改变。index.js和index.css同为一个chunk,如果index.js内容发生变化,但是index.css没有变化,打包后他们的hash都发生变化,这对css文件来说是一种浪费。contentHash将根据资源内容创建出唯一hash,也就是说内容不变,hash不变 -
总结:
hash要变一起变;chunkhash公共内容hash不发生改变,其他一起变;contenthash哪个文件内容发生变化,那么对应的hash才发生改变
五、path、publicPath、contentBase
output.publicPath 和 devServer.publicPath
output里面的publicPath表示的是打包生成的index.html文件里面引用资源的前缀;就是项目要扔到服务器的哪个地址里面devServer里面的publicPath表示的是打包生成的 静态文件 所在的位置- 配置了
devServer.publicPath之后,打完包的项目会自动在请求地址上加上你所配置的那个文件夹名字,比如说我们配置的address_v2,那么此时请求的所有文件资源都是携带这个address_v2,也就是说在你的请求地址最后面要加上address_v2,那么在服务器上也需要放这么一个文件夹,用来放置你打包完的项目 devServer.contentBase是指开发环境下服务器根目录
六、webpack 处理 css 的一些介绍
css loader (包括前处理器和后处理器)
-
css 基础 loader
- css-loader 和 style-loader
-
css 前处理 less 两件套
- less 和 less-loader
-
css 前处理 sass 两件套
- node-sass 和 sass-loader
-
css 后处理 postcss 两件套
- postcss-loader 和 autoprefixer
- postcss 需要配置, 需要一个 postcss.config.js 配置文件
module.exports = {
plugins: [require('autoprefixer')],
};
本篇到此基本也就告一段落了,下一篇介绍 webpack 常用配置以及可能遇到的坑