webpack基础简介
1. 前端工程化
本质上就是将前端开发流程,模块化、组件化、规范化、自动化。通过规范和工具来提高前端应用质量及开发效率。
模块化
模块化就是将一个大文件拆分成相互依赖的小文件,再进行统一的拼装和加载。不论是js、css还是资源,是对代码或者资源的拆分。(import/require)
组件化
与模块化不同,组件化更多指的是基于UI的组件,包含模版(html)、样式(css)、逻辑交互(js)。
规范化
语法规范,开发规范(eslint)。
自动化
脚手架(快速生成项目目录模版)、构建工具、部署工具。
2. 构建工具
构建工具能为我们做什么
- 转换es6语法
- 转换JSX(框架语法糖)
- CSS前缀补全/预处理器(less/sass/stylus)
- 压缩
- 生成sourceMap
前端流行的构建工具,Grunt、Gulp、Webpack。
Grunt、Gulp
基于任务流(工作流),将功能拆解成固定步骤的任务,例如可以分为解析html、解析css、解析js,包括代码和图片压缩。
打包的思路是:遍历源文件→匹配规则→打包,这个过程中做不到按需加载,即对于打包起来的资源,到底页面用不用,打包过程中是不关心的。
Grunt的缺点是磁盘IO,每一次任务都需要从磁盘中读取文件,处理完后再写入到磁盘,所以构建速度较慢。
Gulp解决了Grunt的这个问题,每一次任务的构建结果会放在内存里,下一个任务使用上一个任务的结果,加快打包速度。
Webpack
Webpack认为一切皆模块,文件之间相互引用,会形成一个依赖关系,利用这个依赖关系对文件进行跟踪,并将各个模块通过loader和plugins处理,生成最后的构建产物。抹平浏览器之间差异,将原本浏览器不能识别的规范和各种各样的静态文件进行分析,压缩,合并,打包,最后生成浏览器支持的代码。
3. Webpack定义
Webpack是Javascript应用的静态模块打包器(static module bundler),构建工具。
在 Webpack 处理应用程序时,它会在内部创建一个依赖图(dependency graph),用于映射到项目需要的每个模块,然后将所有这些依赖生成到一个或多个 bundle。
4. Get started
安装
- 需要node环境
- 创建空项目
- 初始化npm
- 安装webpack、webpack-cli
$ mkdir webpack-test
$ cd webpack-test
$ npm init -y
$ npm i webpack webpack-cli -D
此时的目录结构
创建src目录,存放代码
- 创建src/index.js文件
import say from './enums' function foo() { console.log(`${ say } world!`) } foo()- 创建src/enums.js,导出一个字符串
export default 'hello'在命令行中执行打包
$ npx webpack
Hash: b751b6e5d57a02d00c49
Version: webpack 4.43.0
Time: 371ms
Built at: 2020/06/14 下午4:38:37
Asset Size Chunks Chunk Names
main.js 979 bytes 0 [emitted] main
Entrypoint main = main.js
[0] ./src/index.js + 1 modules 402 bytes {0} [built]
| ./src/index.js 232 bytes [built]
| ./src/enums.js 170 bytes [built]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
npx解决的主要问题是调用项目内部安装的模块,可以在命令行中调用,不必配置在package.json文件的scripts字段里。 http://www.ruanyifeng.com/blog/2019/02/npx.html
此时项目目录结构
- 运行结果
$ node ./dist/main.js
hello world!
warning
在刚才的构建结果中,可以看到最后有一个warning
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
提示我们并未给mode选项赋值,webpack默认是生产环境打包,并且mode有三个选项:development(开发)、production(生产)和none,不同环境会有不同的默认配置。
5. webpack打包配置
配置方式:
- 命令行中输入指令
- 配置文件
命令行中输入指令方式(webpack-cli)
使用webpack-cli的形式配置不灵活,且不直观。
webpack-cli 命令的选项比较多,详细可以通过 webpack-cli 的文档进行查阅。
常用选项:
--config: 指定一个 Webpack 配置文件的路径--mode: 指定打包环境的 mode,取值为development和production,分别对应着开发环境和生产环境;–-json:输出 Webpack 打包的结果,可以使用webpack --json > stats.json方式将打包结果输出到指定的文件;–-progress:显示 Webpack 打包进度,百分比;–-watch, -w:watch 模式打包,监控文件变化之后重新开始打包,但是需要手动刷新浏览器才能看到打包后结果;-–profile:会详细的输出每个环节的用时(时间),方便排查打包速度瓶颈。
配置文件
Webpack 是可配置的模块打包工具,我们可以通过修改 Webpack 的配置文件(webpack.config.js)来对 Webpack 进行配置。配置文件名默认为webpack.config.js,也就是说webpack会自动去查找这个文件的配置,如希望更改默认配置文件名引用其他文件,可以使用webpack-cli中--config选项更改。
$ npx webpack --config webpack.dev.js
简单的 webpack.config.js 示例
const path = require('path');
module.exports = {
mode: 'development',
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js'
}
};
上面示例中,使用 CommonJS 的require引入 Node.js 内置的path模块,然后通过module.exports将 Webpack 的配置导出。
Webpack 配置支持多种语言
Webpack 不仅仅支持 js 配置,还支持 ts(TypeScript)、CoffeeScript 甚至 JSX 语法的配置,不同语言其实核心配置项都不变,只不过语法不同而已。
- Typescript
import * as path from 'path';
import * as webpack from 'webpack';
const config: webpack.Configuration = {
mode: 'production',
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js'
}
};
export default config;
- CoffeeScript
HtmlWebpackPlugin = require('html-webpack-plugin')
webpack = require('webpack')
path = require('path')
config =
mode: 'production'
entry: './path/to/my/entry/file.js'
output:
path: path.resolve(__dirname, 'dist')
filename: 'my-first-webpack.bundle.js'
module: rules: [ {
test: /\.(js|jsx)$/
use: 'babel-loader'
} ]
plugins: [
new HtmlWebpackPlugin(template: './src/index.html')
]
module.exports = config
- JSX
import jsxobj from 'jsxobj';
// example of an imported plugin
const CustomPlugin = config => ({
...config,
name: 'custom-plugin'
});
export default (
<webpack target="web" watch mode="production">
<entry path="src/index.js" />
<resolve>
<alias {...{
react: 'preact-compat',
'react-dom': 'preact-compat'
}} />
</resolve>
<plugins>
<CustomPlugin foo="bar" />
</plugins>
</webpack>
);
Webpack 配置形式
- 导出一个对象
const path = require('path');
module.exports = {
mode: 'development',
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js'
}
};
- 导出一个函数
module.exports = (env, argv) => {
return {
mode: env.production ? 'production' : 'development',
devtool: env.production ? 'source-maps' : 'eval',
plugins: [
new TerserPlugin({
terserOptions: {
compress: argv['optimize-minimize'] // 只有传入 -p 或 --optimize-minimize
}
})
]
};
};
函数有两个参数
第一个参数是环境,取值来源是webpack-cli中的env选项。
$ webpack --env.production # 设置 env.production == true
第二个参数是选项字典,取值来源是webpack-cli中的其他选项。
$ webpack --env.production --optimize-minimize
- Promise 类型的设置 如果需要异步加载一些 Webpack 配置需要做的变量,那么可以使用 Promise 的方式来做 Webpack 的配置,具体方式如下:
module.exports = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
entry: './app.js'
/* ... */
});
}, 5000);
});
};
- 多配置数组
在一些特定的场景,我们可能需要一次打包多个配置,数组中可以包含每份配置,并且每份配置都会执行一遍构建。
var path = require("path");
module.exports = [
{
name: "mobile",
entry: "./src/mobile",
output: {
path: path.join(__dirname, "dist"),
filename: "mobile.js"
}
},
{
name: "browser",
entry: "./src/browser",
output: {
path: path.join(__dirname, "dist"),
filename: "browser.js"
}
}
];
构建结果
$ npx webpack
Hash: 407a9f8951d944eaa3106a00270d2333ca0ec288
Version: webpack 4.43.0
Child mobile:
Hash: 407a9f8951d944eaa310
Time: 442ms
Built at: 2020/06/14 下午8:12:22
Asset Size Chunks Chunk Names
mobile.js 996 bytes 0 [emitted] main
Entrypoint main = mobile.js
[0] ./src/mobile.js + 1 modules 410 bytes {0} [built]
| ./src/mobile.js 233 bytes [built]
| ./src/enums.js 177 bytes [built]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
Child browser:
Hash: 6a00270d2333ca0ec288
Time: 418ms
Built at: 2020/06/14 下午8:12:22
Asset Size Chunks Chunk Names
browser.js 997 bytes 0 [emitted] main
Entrypoint main = browser.js
[0] ./src/browser.js + 1 modules 411 bytes {0} [built]
| ./src/browser.js 234 bytes [built]
| ./src/enums.js 177 bytes [built]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
6. webpack常见配置选项
mode
mode用来指定当前的构建环境是:production、development还是none。
当不设置的时候,webpack默认环境是生产。
比如当设置环境是development的时候,webpack会默认开启开发阶段实用的参数。
同理,当设置环境是production时,webpack会默认开启生产阶段比较实用的参数。
设置成none的时候,webpack不会帮我们做任何配置。
mode的功能:
| 选项 | 描述 |
|---|---|
| development | 设置为development相当于将process.env.NODE_ENV的值设置为development。开启NamedChunksPlugin和NamedModulesPlugin。 |
| production | 设置为production相当于将process.env.NODE_ENV的值设置为production。开启FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin,OccurrenceOrderPlugin,SideEffectsFlagPlugin,TerserPlugin. |
Mode: development
// webpack.development.config.js
module.exports = {
+ mode: 'development'
- devtool: 'eval',
- cache: true,
- performance: {
- hints: false
- },
- output: {
- pathinfo: true
- },
- optimization: {
- namedModules: true,
- namedChunks: true,
- nodeEnv: 'development',
- flagIncludedChunks: false,
- occurrenceOrder: false,
- concatenateModules: false,
- splitChunks: {
- hidePathInfo: false,
- minSize: 10000,
- maxAsyncRequests: Infinity,
- maxInitialRequests: Infinity,
- },
- noEmitOnErrors: false,
- checkWasmTypes: false,
- minimize: false,
- removeAvailableModules: false
- },
- plugins: [
- new webpack.NamedModulesPlugin(),
- new webpack.NamedChunksPlugin(),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}
Mode: production
// webpack.production.config.js
module.exports = {
+ mode: 'production',
- performance: {
- hints: 'warning'
- },
- output: {
- pathinfo: false
- },
- optimization: {
- namedModules: false,
- namedChunks: false,
- nodeEnv: 'production',
- flagIncludedChunks: true,
- occurrenceOrder: true,
- concatenateModules: true,
- splitChunks: {
- hidePathInfo: true,
- minSize: 30000,
- maxAsyncRequests: 5,
- maxInitialRequests: 3,
- },
- noEmitOnErrors: true,
- checkWasmTypes: true,
- minimize: true,
- },
- plugins: [
- new TerserPlugin(/* ... */),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
- new webpack.optimize.ModuleConcatenationPlugin(),
- new webpack.NoEmitOnErrorsPlugin()
- ]
}
Mode: none
// webpack.custom.config.js
module.exports = {
+ mode: 'none',
- performance: {
- hints: false
- },
- optimization: {
- flagIncludedChunks: false,
- occurrenceOrder: false,
- concatenateModules: false,
- splitChunks: {
- hidePathInfo: false,
- minSize: 10000,
- maxAsyncRequests: Infinity,
- maxInitialRequests: Infinity,
- },
- noEmitOnErrors: false,
- checkWasmTypes: false,
- minimize: false,
- },
- plugins: []
}
当我们想根据不同的mode变量去构建不同的配置的时候,就需要导出一个函数,而不是一个对象了。
var config = {
entry: './src/index.js'
//...
};
module.exports = (env, argv) => {
if (argv.mode === 'development') {
config.devtool = 'source-map';
config.output = {
path: path.join(__dirname, "dist"),
filename: "mobile.js"
}
}
if (argv.mode === 'production') {
config.output = {
path: path.join(__dirname, "dist"),
filename: "browser.js"
}
}
return config;
};
使用
$ npx webpack --mode=development
entry
webpack作为一个模块打包器,webpack中一切皆模块。webpack不仅认为js、css这些代码是模块,同时也认为图片、字体等都是模块,在打包时模块之间是存在依赖关系的。所以webpack从入口文件开始去找这些以来,构成一个依赖树。根据依赖树进行打包。
entry支持多种类型,包括字符串、对象、数组。从作用上来说,包括了单文件入口和多文件入口两种方式。
单文件入口
单文件用法:
字符串、对象、数组.
// 字符串方式
module.exports = {
entry: 'path/to/my/entry/file.js'
};
// 或者使用对象方式
module.exports = {
entry: {
main: 'path/to/my/entry/file.js'
}
};
// 数组方式
module.exports = {
entry: ['./app.js', 'lodash']
};
对象形式是最完整的entry配置,其他形式只是它的简化形式。
key
key可以是简单的字符串,比如:'app', 'main', 'entry-1'等。并且对应着output.filename配置中的[name]变量
module.exports = {
entry: {
'app-entry': './src/mobile.js'
},
output: {
path: path.resolve(__dirname,'./output'),
filename: '[name].js'
}
}
使用上面的配置,构建结果:
| => npx webpack --mode=development
Hash: f4990d156cba59d24a35
Version: webpack 4.43.0
Time: 91ms
Built at: 2020/06/15 下午2:58:22
Asset Size Chunks Chunk Names
app-entry.js 4.8 KiB app-entry [emitted] app-entry
Entrypoint app-entry = app-entry.js
[./src/enums.js] 177 bytes {app-entry} [built]
[./src/mobile.js] 233 bytes {app-entry} [built]
value
value可以是数组,可以是字符串。
value如果是字符串,而且必须是合理的noderequire函数参数字符串。比如文件路径:'./app.js'(require('./app.js'));比如安装的npm模块:'lodash'(require('lodash'))
module.exports = {
entry: {
'app-entry': './src/mobile.js',
'app-lodash': 'lodash'
},
output: {
path: path.resolve(__dirname,'./output'),
filename: '[name].js'
}
}
使用上面的配置,构建结果:
| => npx webpack --mode=development
Hash: ab20bcd1500d996505a9
Version: webpack 4.43.0
Time: 386ms
Built at: 2020/06/15 下午3:15:58
Asset Size Chunks Chunk Names
app-entry.js 4.85 KiB app-entry [emitted] app-entry
app-lodash.js 551 KiB app-lodash [emitted] app-lodash
Entrypoint app-entry = app-entry.js
Entrypoint app-lodash = app-lodash.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {app-lodash} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {app-lodash} [built]
[./src/enums.js] 173 bytes {app-entry} [built]
[./src/mobile.js] 288 bytes {app-entry} [built]
+ 1 hidden module
value如果是数组,则数组中元素需要是上面描述的合理字符串值。数组中的文件一般是没有相互依赖关系的,但是又处于某些原因需要将它们打包在一起。比如:
entry: {
vendor: ['jquery', 'lodash']
}
多文件入口
entry必须是一个对象
值得注意的是,目前入口文件只支持js,其他的如html和css均不支持。
module.exports = {
entry: {
home: 'path/to/my/entry/home.js',
search: 'path/to/my/entry/search.js',
list: 'path/to/my/entry/list.js'
}
};
output
output用来告诉webpack如何将编译后的文件输出到磁盘,指定输出文件的目录及名称.
entry对应于源代码,output对应于构建结果代码。
即使可以存在多个入口起点,但只指定一个输出配置。对于不同的entry可以通过output.filename占位符语法来区分。
output的常用属性是:
path:输出目录绝对路径
filename:输出文件的文件名.
当不指定 output 的时候,默认输出到 dist/main.js,即 output.path 是dist,output.filename 是 main。
'use strict'
const path = require('path')
module.exports = {
mode: 'production',
entry: {
index: './src/index.js',
search: './src/search.js'
},
output: {
path: path.join(__dirname,'dist'),
filename: '[name].js'
}
}
代码中filename中的[name]为占位符,通过占位符确保文件名称的唯一。它对应的是entry的key(index、search),所以最终输出结果是:
./src/index.js → dist/index.js
./src/search.js → dist/search.js
常用占位符:
| 占位符 | 含义 |
|---|---|
[hash] |
项目hash。和整个项目的构建相关,只要项目文件有修改,整个项目构建的hash值就会改变。也就是说修改任意一个文件,所有文件的hash都会改变。 |
[chunkhash] |
和webpack打包的chunk有关,不同的entry会生成不同的chunkhash。 |
[name] |
模块名称 |
[id] |
模块标识符 |
[hash] 和 [chunkhash] 的长度可以使用 [hash:16](默认为 20)来指定。或者,通过指定 output.hashDigestLength 在全局配置长度。
总结
介绍了前端工程化,构建工具,webpack打包配置的方式和形式,以及webpack的部分配置选项。
参考:
https://www.jianshu.com/p/88ed70476adb https://juejin.cn/post/6844904132512317453#heading-6 https://www.jianshu.com/p/3e8941eda2dd https://www.jianshu.com/p/fe96491ccf56
本文使用 mdnice 排版