webpack 模块化的配置,包括处理 js转换 / css / html / eslint / 图片等的 loader、plugin 配置。
传说中前端配置工程师
的基本修养……
一 模块(Module)
模块
是为了完成某种功能所需的程序或子程序,模块是系统中职责单一
且可替换
的部分。
模块化
就是把系统代码分为一系列职责单一且可替换的模块。模块化开发
是指如何开发新的模块和复用已有的模块来实现应用的功能。
javascript 的三大主流模块规范已在上篇介绍过,这么不多赘述。
Webpack 中一切皆模块,包括 javascript、css、html、图片、字体、富媒体及 less、sass、 各种 javascript 库等等,webpack 可以针对不同的模块用不同的 loader 做不同的解析编译,做到按需加载。比如对于 less 代码,可以先用 less-loader 转换为 css 代码,用 css-loader 处理其中的 url/import 等逻辑,最后用 style-loader 把 css 插入到 <style>
标签里。
1. Webpack对模块的增强:import() 和神奇注释(Magic Comments)
Webpack 导入模块的方式不限于三大主流模块规范,提供了 import() 引入模块的方式。
与 import from 不同的是, import from 是静态分析打包的,而 import() 是动态分析打包的,通过异步的方式加载模块,import() 返回一个 Promise 对象。并且 import() 被视为分割点,被请求的模块和它引用的所有子模块,打包后会分割到一个单独的 chunk 中。
// 想象我们有一个从 cookies 或其他存储中获取语言的方法
const language = detectVisitorLanguage();
import(`./locale/${language}.json`).then((module) => {
// do something with the translations
});
神奇注释(Magic Comments),是指可以通过在 import() 中添加注释实现对打包的文件做一定的操作,比如给 chunk 命名、选择不同模式。
// 单个目标
import(
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
/* webpackExports: ["default", "named"] */
'module'
);
// 多个可能的目标
import(
/* webpackInclude: /\.json$/ */
/* webpackExclude: /\.noimport\.json$/ */
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
/* webpackPreload: true */
`./locale/${language}`
);
Magic Comments | 说明 |
---|---|
webpackChunkName | chunk 文件的名称 |
webpackInclude | 在导入解析过程中,用于匹配的正则表达式,只有匹配到的模块才会被打包 |
webpackExclude | 用于匹配的正则表达式,匹配到的模块不会被打包 |
webpackMode | 指定以不同的模式解析动态导入,可选值lazy/lazy-once/eager/weak |
webpackPrefetch | 是否预取模块,及其优先级 |
webpackPreload | 是否预加载模块,及其优先级 |
通过神奇注释,import()不再是简单的 JavaScript 异步加载器,还是任意模块资源的加载器,举例说明:如果我们页面用到的图片都放在src/assets/img文件夹下,你们可以通过下面方式将用到的图片打包到一起:
import(/* webpackChunkName: "image", webpackInclude: /\.(png|jpg|gif)/ */ './assets/img');
2. Webpack对模块的增强:require
require.context()
require.context(directory, includeSubdirs, filter)
可以批量将 directory内的文件全部引入进文件,并且返回一个具有resolve的 context 对象,使用context.resolve(moduleId)
则返回对应的模块。该功能十分有用。
- directory:目录 string;
- includeSubdirs:是否包含子目录,可选,默认值是 true;
- filter:过滤正则规则,可选项。
require.context() 会将所有的文件都引入进 bundle。
require.resolve()
require.resolve(dependency: String)
可以获取模块的唯一 ID, 并把模块真实引入 bundle。
require.include()
require.include(dependency: String)
引入一个不需要执行的 dependency,这样可以用于优化输出 chunk 中依赖模块的位置。
/**
* 输出:
* entry chunk: file.js and a
* anonymous chunk: b
* anonymous chunk: c
* 不使用 require.include('a'),输出的两个匿名 chunk 都会有模块 a
*/
require.include('a');
require.ensure(['a', 'b'], function (require) {
/* ... */
});
require.ensure(['a', 'c'], function (require) {
/* ... */
});
二 使用 Babel 转换 js 代码
在 webpack 中编写 JavaScript 代码,可以使用最新的 ES 语法,而最终打包的时候,webpack 会借助 Babel 将 ES6+语法转换成在目标浏览器可执行 ES5 语法。
Babel 拥有自己的 cli,可以单独安装使用,具体参考 Babel 官网,这里不赘述。
在 npm 项目中,Babel 支持两种文件配置方式:
- 在 package.json 中配置 babel 属性
- 在项目根目录下单独创建 .babelrc 或者 .babelrc.js 文件
// package.json
{
"name": "my-package",
"version": "1.0.0",
"babel": {
"presets": ["@babel/preset-env"]
}
}
// .babelrc
{
"presets": ["@babel/preset-env"]
}
如果使用了webpack,也可以配置在 webpack 的配置文件中:
// 安装开发依赖
npm i webpack babel-loader webpack-cli @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
// 将 runtime 作为依赖
npm i @babel/runtime -S
// 配置示例
module.exports = {
entry: './babel.js',
mode: 'development',
devtool: false,
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage'
}
]
]
}
}
]
}
]
}
};
1. env 选项
配置 env 选项可以实现在不同环境下使用不同的 Babel 配置。
env 选项的值将从 process.env.BABEL_ENV 获取,如果没有的话,则获取 process.env.NODE_ENV 的值,如果这个也没有,会默认设置为 "development"。
{
"env": {
"production": {
"presets": ["@babel/preset-env"]
}
}
}
2. Polyfill
polyfill:在 JavaScript 中表示一些可以抹平浏览器实现差异的代码,比如某浏览器不支持 Promise,可以引入es6-promise-polyfill 等库来解决。
Babel 的插件分为两类:转换插件和语法解析插件。转换插件用于对语法进行转换,如 ES6 转换为 ES5,语法解析插件用来扩展语法,如解析 React 特殊设计的jsx
语法。对于各种情况下的语法转换与解析,需要一个一个的引入相应的插件,这种方式比较麻烦,一般场景下,可以直接使用官方推出的插件组合@babel/preset-env
。
@babel/preset-env
可以根据开发者的配置按需加载对应的插件,还可以根据代码执行平台环境和具体浏览器版本生成相应的 js 代码。
@babel/preset-env
有两个重要的配置项:useBuiltIns
和target
。
/**
* useBuiltIns: usage|entry|false,一般采用 usage 这种方式,即指定按需加载。
* target: 指定构建目标
*/
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage|entry|false"
"targets": {
// "browsers": "IE 10" // 指定IE 10浏览器
"node": "8.9.3" // 如果在node环境中,指定node版本号
}
}]
]
}
Browserslist 目标浏览器的设置
项目的实际开发与运行中,很多情况下要做一定的浏览器兼容,而 Browserslist 就是用来设置目标浏览器的工具。许多开发工具包括 npm、babel 都兼容内置了 Browserslist。
Browserslist 的配置可以放在 package.json 中,也可以单独放在配置文件.browserslistrc 中。相关的开发工具都会主动查找 browserslist 的配置文件,根据 browserslist 配置找出对应的目标浏览器集合。
// package.json 内配置
{
"browserslist": ["last 2 version", "> 1%", "maintained node versions", "not ie < 11"]
}
// 兼顾不同环境的配置,设置BROWSERSLIST_ENV 或者 NODE_ENV可以配置不同的环境变量
{
"browserslist": {
"production": ["> 1%", "ie 10"],
"development": ["last 1 chrome version", "last 1 firefox version"]
}
}
// 项目的根目录下.browerslistrc文件:
// 注释是这样写的,以#号开头
// 每行一个浏览器集合描述
last 2 version
> 1%
maintained node versions
not ie < 11
// 兼顾不同环境的配置
[production staging]
> 1%
ie 10
[development]
last 1 chrome version
last 1 firefox version
常见的浏览器集合范围说明:
范围 | 说明 |
---|---|
last 2 versions | caniuse.com网站跟踪的最新两个版本,假如 iOS 12 是最新版本,那么向后兼容两个版本就是 iOS 11 和 iOS 12 |
> 1% | 全球超过 1%人使用的浏览器,类似> 5% in US则指代美国 5%以上用户 |
cover 99.5% | 覆盖 99.5%主流浏览器 |
chrome > 50 ie 6-8 | 指定某个浏览器版本范围 |
unreleased versions | 说有浏览器的 beta 版本 |
not ie < 11 | 排除 ie11 以下版本不兼容 |
since 2013 last 2 years | 某时间范围发布的所有浏览器版本 |
maintained node versions | 所有被 node 基金会维护的 node 版本 |
current node | 当前环境的 node 版本 |
dead | 通过last 2 versions筛选的浏览器中,全球使用率低于0.5%且官方声明不在维护或者事实上已经两年没有再更新的版本 |
defaults | 默认配置,> 0.5% last 2 versions Firefox ESR not dead |
浏览器名称列表(大小写不敏感):
浏览器 | 说明 | 浏览器 | 说明 |
---|---|---|---|
Chrome | chrome浏览器 | ChromeAndroid/and_chr | chrome 安卓移动浏览器 |
Edge | Edge浏览器 | Explorer/ie | ie浏览器 |
Android | 安卓webview浏览器 | Baidu | 百度浏览器 |
Firefox/ff | 火狐浏览器 | FirefoxAndroid/and_ff | 火狐安卓浏览器 |
iOS/ios_saf | iOS Safari 浏览器 | ExplorerMobile/ie_mob | ie 移动浏览器 |
Safari | 桌面版本 Safari | Node | nodejs |
Opera | opera浏览器 | OperaMobile/op_mob | opera 移动浏览器 |
OperaMini/op_mini | operaMini 浏览器 | QQAndroid/and_qq | QQ 安卓浏览器 |
Samsung | 三星浏览器 | UCAndroid/and_uc | UC 安卓浏览器 |
Electron | Electron | BlackBerry / bb | 黑莓浏览器 |
Babel Polyfill 的最佳实践:
useBuiltIns:'usage'可以近乎完美的解决我们的 Polyfill 问题,它是按需引入模块,根据 .browserslist + 业务实际代码来设置引入 Polyfill,不会多余的引入。
三 样式相关配置
Webpack 中一切皆模块,经 loader 处理后的 CSS 可以在 js 中被直接引用。
1. css-loader 与 style-loader
// 引入 css-loader
npm install --save-dev css-loader
// webpack.config.js 配置loader
{
module: {
rules: [
{
test: /\.css$/,
use: ['css-loader']
}
];
}
}
// 代码中引入 css 模块
import css from './css/index.css';
console.log(css);
// 或者不用webpack配置文件,直接在引入语句中使用loader
import css from 'css-loader!./css/index.css';
console.log(css);
经 css-loader 处理后,css 模块被打包转换成 js 对象,可直接用 js 语法使用。
style-loader 的作用是将 css-loader 打包好的 css 代码以<style>
标签的形式插入到 html 文件中,一般情况下,在一个项目中 style-loader 与 css-loader 是成对出现的,并且 style-loader 要在 css-loader 之后。
npm install --save-dev style-loader
2. mini-css-extract-plugin
CSS 作为<style>
标签放到 HTML 内还是不够的,我们还需要将 CSS 以 <link>
的方式通过 URL 的方式引入进来,这时候就需要使用 mini-css-extract-plugin
。
npm install --save-dev mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
// 添加 plugin
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
})
],
module: {
rules: [
{
test: /\.css$/,
// 添加 loader
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
}
};
3. CSS Modules
CSS Modules 指的是所有的 CSS 类名及其动画名都只是局部作用域的 CSS 文件。CSS Modules 主要解决的问题有:
- 解决 CSS 类都是全局的,容易造成全局污染(样式冲突);
- JS 和 CSS 共享类名;
- 可以方便的编写出更加健壮和扩展方便的 CSS。
CSS Module 的表现形式:
/* app.css */
.element {
background-color: blue;
color: white;
font-size: 24px;
}
/* app.js */
// 引入 app.css
import styles from './app.css';
// js 直接使用 CSS 的类名作为对象值
let element = `
<div class="${styles.element}">
<p>CSS Modules</p>
</div>
`;
document.write(element);
CSS Modules 在 webpack.config.js 中的配置:
// CSS Modules 相关的配置还有很多,可以查看css-loader对应的文档。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}
};
4. CSS 预处理器
常见的 CSS 预处理器有 Less、Sass 及其语法变种 Scss和Stylus。预处理器为 CSS 提供了变量、函数、运算、作用域、继承、嵌套等语法,使其功能更加强大。
以 less-loader 为例,less-loader 将 Less 语法编译成 CSS,后续还需要使用 css-loader 和 style-loader 处理才可以,所以一般来说需要配合使用:
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true
}
},
'less-loader' // 将 Less 编译为 CSS
]
}
]
}
};
一些预处理语言需要安装对应的解析器,例如 sass-loader,需要同时安装 node-sass。
npm install sass-loader node-sass --save-dev
5. PostCSS:CSS 后处理器
表现形式:
/*没有前缀的写法*/
.flex {
display: flex;
}
/*经过 postcss autoprefixer 处理后*/
.flex {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
PostCss 能实现的功能很多,除了添加前缀,还可以最新语法转义、压缩等,甚至可以扩展 CSS 的语言特性。配置了 postcss-loader 之后,WebPack 就可以使用 PostCSS 来处理 CSS了,但是 PostCSS 本身只不过是将 CSS 解析成 AST ,真正起作用的还需要依赖其强大的插件系统。
PostCSS 配置其实主要是配置其使用哪些插件,PostCSS 的配置写法有以下三种方式:
- 通过配置文件postcss.config.js,一般放置在项目的根目录下;
- 通过 loader 的配置项 options;
- 直接在 package.json 中添加个 postcss 属性。
/* postcss.config.js */
// 引入postcss 插件
const autoprefixer = require('autoprefixer');
module.exports = {
plugins: [autoprefixer(['IE 10'])]
};
/* webpack.config.js */
// 引入postcss 插件
const autoprefixer = require('autoprefixer');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
// 通过 plugins 选项
plugins: [autoprefixer(['IE 10'])]
}
}
]
}
]
}
};
/* package.json:受限于 json 的语法,可扩展性较弱,一般不推荐 */
{
"postcss": {
"plugins": {
"autoprefixer": "IE 10"
}
}
}
6. 常见的 PostCss 插件
以下罗列几种常用的 PostCSS 插件,详细用法参见各自的官网或相关文档。
Autoprefixer
给 css 补齐各种浏览器私有的前缀,例如 -webkit、-moz、-ms 等,当然还会处理各种兼容性问题,比如 flex 语法,不能简单添加 -webkit 就解决,还需要处理成 -webkit-box 这类老版本的标准。
Autoprefixer 的主要参数就是 browserslist。
postcss-preset-env
postcss-preset-env 是跟 babel 的 preset-env 类似的功能,不用一一引入要用的插件,在打包构建的时候,会根据不同的配置输出对应支持的 CSS 文件。
PreCSS
可以写类似 Sass 和 cssnext 语法的 CSS。
cssnano
CSS 压缩优化。
四 代码规范工具配置
1. ESLint
npm install eslint --save-dev
安装之后,可以通过使用 npx eslint 直接运行,在运行之前,我们需要创建个 ESLint 的配置文件,使用 eslint --init 命令创建 .eslintrc.json 文件。
.eslintrc.json 内容示例,具体可查看 eslint 官网文档:
{
"env": {
"browser": true,
"es6": true
},
"extends": "airbnb-base",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
// 禁止 console,要用写 eslint disbale
'no-console': 2,
// 禁止 debugger,防止上线
'no-debugger': 2,
// 禁止 alert,要用写 eslint disable
'no-alert': 2,
// 不用的 var,要删除,手动 tree shaking,要洁癖
'no-unused-vars': 2,
// 没定义就用的就别用,全局的要用 写 eslint global
'no-undef': 2
}
}
ESLint 的报错类型包括三种:off、warn和error,分别对应着:0、1、2,所以上面的配置的 rule 实际为error级别的规则,检测到了则直接报错误(Error)。
webpack 中的配置:
npm install eslint-loader --save-dev
module.exports = {
module: {
rules: [
{
test: /\.js$/,
loader: 'eslint-loader',
enforce: 'pre', // 调整了 loader 加载顺序,保证先检测代码风格,之后再做 Babel 转换等工作
include: [path.resolve(__dirname, 'src')], // 指定检查的目录
options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
formatter: require('eslint-friendly-formatter') // 指定错误报告的格式规范
}
}
]
}
};
2. StyleLint
StyleLint 的配置文件是.stylelintrc.json,其中的写法跟 ESLint 的配置类似
npm install -D stylelint
/* .stylelintrc.json */
{
"extends": ["stylelint-config-standard", "stylelint-config-recess-order"],
"rules": {
"at-rule-no-unknown": [true, {"ignoreAtRules": ["mixin", "extend", "content"]}]
}
}
webpack 中配置:
const StyleLintPlugin = require('stylelint-webpack-plugin');
module.exports = {
// ...
plugins: [new StyleLintPlugin(options)]
// ...
};
默认 StyleLint-webpack-plugin 会查找项目中的 StyleLint 配置文件,根据配置文件的配置来检测 CSS 代码。
五 静态资源管理
前端的静态资源一般指图片、字体文件、富媒体等。
1. 图片引入方式
图片引入的一般方式有,Html 引入,Css 引入和 Javascript 的引入。在 webpack 中可以使用 loader 来处理加载图片。
file-loader 与 url-loader
这是最常见的处理图片的两种 loader。它俩区别在于:
- file-loader: 能够根据配置项复制使用到的资源和构建之后的文件夹,并且能够更改对应的链接
- url-loader: 包含 file-loader 的全部功能,并且能欧根据配置将文件转换成 Base64 的形式,以此减少 http 请求,实现一定程度上的优化。如果图片较大,Base64 会很长,会导致打包后的 js/css 文件很大。
webpack 的配置如下,使用 publicPath 可做静态资源的 CDN 处理,可以使用 resolve.alias 做别名处理:
// webpack.config.js
const HTMLPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
// output: {
// publicPath: 'http://bd.bxstatic.com/img/'
// }
devtool: false,
module: {
// resolve: {
// alias: {
// '@assets': path.resolve(__dirname, './src/assets')
// }
// },
rules: [
{
test: /\.html$/,
use: ['html-loader']
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(png|svg|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
// url-loader 默认会做 Base64 处理,这里限制不超过 3k 大小的图片才做 Base64 处理
limit: 3*1024
}
}
}
]
},
plugins: [
new HTMLPlugin({
template: './src/index.html'
})
]
};
在 Html 和 Css 中用别名的方式引入图片时,要在别名前加~
符号:
<img src="~@assets/img/large.png" alt="背景图" />
.bg-img {
background: url(~@assets/img/small.png) no-repeat;
}
svg-url-loader
svg-url-loader 的工作原理类似于 url-loader,主要用来处理 SVG 文件。 svg-url-loader 拥有改善 IE 浏览器支持的选项,但是在其他浏览器中很糟糕。如果你需要兼容 IE 浏览器,设置 iesafe: true选项。
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
loader: 'svg-url-loader',
options: {
// 小于 10kB(10240字节)的内联文件
limit: 10 * 1024,
// 移除 url 中的引号
// (在大多数情况下它们都不是必要的)
noquotes: true
}
}
]
}
};
img-webpack-loader
img-webpack-loader 用来对图片进行压缩优化,支持 JPG、PNG、GIF 和 SVG 格式的图片,不过必须和 url-loader 以及 svg-url-loader 一起使用。
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/,
loader: 'image-webpack-loader',
//提高优先级,保证在url-loader和svg-url-loader之前完成图片优化
enforce: 'pre'
}
]
}
};
postcss-sprites
将小图标合成雪碧图。需要结合 postcss-loader 使用。
// postcss.config.js
const postcssSprites = require('postcss-sprites');
module.exports = {
plugins: [
postcssSprites({
// 在这里制定了从哪里加载的图片被主动使用css sprite
// 可以约定好一个目录名称规范,防止全部图片都被处理
spritePath: './src/assets/img/'
})
]
};
//webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader'
}
]
}
]
}
};
2. 字体、富媒体处理
可以直接使用 url-loader 或者 file-loader 进行配置即可,不需要额外的操作。如果不需要 Base64,那么可以直接使用 file-loader,需要的话就是用 url-loader。
{
// 文件解析
test: /\.(eot|woff|ttf|woff2|appcache|mp4|pdf)(\?|$)/,
loader: 'file-loader',
query: {
// 这么多文件,ext不同,所以需要使用[ext]
name: 'assets/[name].[hash:7].[ext]'
}
}
3. 数据
类似 CSV、TSV 和 XML 等数据,那么我们需要单独给它们配置相应的 loader。内置支持 JSON 数据。
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader'
]
},
{
test: /\.xml$/,
use: [
'xml-loader'
]
}
六 HTML 打包配置
webpack 处理 html 页面,一般是使用 html-webpack-plugin
插件。
// 安装
npm install html-webpack-plugin --save-dev
简单配置示例
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [new HtmlWebPackPlugin()]
// 指定打包后页面的名称(默认index.html)和 title
// plugins: [new HtmlWebPackPlugin({title: 'hello', filename: 'foo.html'})]
};
打包后会生成 index.html,内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Webpack App</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<script src="main.js"></script>
</body>
</html>
指定Template打包
指定一个 template,使打包后的 html 页面是依据这个 template 来打包的,这样 html 的内容我们可以自定义,比如我们需要打包后的文件有<div id="app"></div>
节点。
指定 template:
<!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>
<h1>hello world</h1>
<div id="app"></div>
</body>
</html>
webpack.config.js 配置:
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [
new HtmlWebPackPlugin({
template: './src/index.html' // 指定模板
})
]
};
打包后的 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>
<h1>hello world</h1>
<div id="app"></div>
<script src="main.js"></script></body>
</html>
多页配置
单一入口:
const HtmlWebPackPlugin = require('html-webpack-plugin');
const indexPage = new HtmlWebPackPlugin({
template: './src/index.html',
filename: 'index.html'
});
const listPage = new HtmlWebPackPlugin({
template: './src/list.html',
filename: 'list.html'
});
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [indexPage, listPage]
};
多入口:
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
list: './src/list.js'
},
plugins: [
new HtmlWebPackPlugin({template: './src/index.html', filename: 'index.html', chunks: ['index']}),
new HtmlWebPackPlugin({template: './src/list.html', filename: 'list.html', chunks: ['list']})
]
};
最佳实践:
/**
* 1. /scripts/utils/index.js
* 使用 npm 库 globby 读取src/pages/*.js, 生成入口 entry
*/
const path = require('path');
const globby = require('globby');
const getEntry = (exports.getEntry = () => {
// 异步方式获取所有的路径
const paths = globby.sync('./pages/*.js', {
cwd: path.join(__dirname, './src')
});
const rs = {};
paths.forEach(v => {
// 计算 filename
const name = path.basename(v, '.js');
let p = path.join('./src', v);
if (!p.startsWith('.')) {
// 转成相对地址
p = './' + p;
}
rs[name] = p;
});
return rs;
});
console.log(getEntry());
/**
* 2. /scripts/utils/index.js
* 遍历 entry,生成 html-webpack-plugins 数组
*/
const HtmlWebPackPlugin = require('html-webpack-plugin');
exports.getHtmlWebpackPlugins = () => {
const entries = getEntry();
return Object.keys(entries).reduce((plugins, filename) => {
plugins.push(
new HtmlWebPackPlugin({
template: entries[filename],
filename: `${filename}.html`,
chunks: [filename]
})
);
return plugins;
}, []);
};
/**
* 3. webpack.config.js
* require 封装好的html-webpack-plugins 数组
*/
const {getEntry, getHtmlWebpackPlugins} = require('./scripts/utils');
module.exports = {
mode: 'development',
getEntry(),
plugins: [
...getHtmlWebpackPlugins()
]
};
七 Webpack Dev Server 本地开发服务
webpack-dev-server 是一个可以用来启动本地服务的插件。通过命令启动对应的服务。
// 安装
npm install webpack-dev-server
// 启动命令
npx webpack-dev-server
/* 命令常用参数 */
// 修改端口号和 host
webpack-dev-server --port 3000 --host 127.0.0.1
// 启动inline 模式的自动刷新
webpack-dev-server --hot --inline
// 手动指定 webpack config 文件
webpack-dev-server --config webpack.xxx.js
// 指定 webpack 的 mode
webpack-dev-server --mode development
// watch 功能,文件发生变化则触发重新编译
webpack-dev-server --watch
// dev-server默认会将工作目录(当前目录)作为基本目录,可以手动修改它
webpack-dev-server --content-base ./build
执行 webpack-dev-server 命令之后,它会读取 Webpack 的配置文件(默认是 webpack.config.js),然后将文件打包到内存中(所以看不到dist文件夹的生产,Webpack 会打包到硬盘上),这时候打开 server 的默认地址:localhost:8080 就可以看到文件目录或者页面(默认是显示 index.html,没有则显示目录)。
一般在项目开发中,启动本地服务的命令会配置在 package.json 的 scripts 属性中:
{
"scripts": {
"dev": "webpack-dev-server --mode development --config webpack.config.dev.js --hot --inline --port 3000"
}
}
更多的时候,是通过 devServer 属性直接配置在 webpack.config.js 中:
const path = require('path');
module.exports = {
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 9000, // 端口
hot: true, // 热部署,开启热部署需要引入 HotModuleReplacementPlugin 插件
inline: true,
// proxy解决本地开发跨域的问题,/api所有请求都转发到了baidu.com
proxy: {
'/api': 'http://baidu.com'
// secure: false // 支持 https
}
// 加载所有内部中间件之前和之后可以插入自定义的中间件
before(app, server) {
app.get('/api/mock.json', (req, res) => {
res.json({hello: 'world'});
});
},
after(){}
},
plugins: [
// 添加 hmr plugin
new webpack.HotModuleReplacementPlugin()
]
};
HMR 即模块热替换(Hot Module Replacement)的简称,它可以在应用运行的时候,不需要刷新页面,就可以直接替换、增删模块。
八 注意事项
开发环境和生产环境的配置可能不同,需要分开、判断处理。
开发 vue 一般通过集成了 webpack 的 vue-cli 创建项目,但如果直接使用 webpack 需要引入 vue-loader
。
记忆思考:
1.什么是 JavaScript 的模块化开发?有哪些可以遵循的规范?
2.在 js 文件中怎么调用 loader 来处理一个模块?
4.怎么实现 Webpack 的按需加载?什么是神奇注释?
5.Babel 的 preset-env 是什么?
6.Babel 怎么做 Polyfill,Polyfill 的最佳实践是什么?
7.Babel 怎么针对不同的浏览器打包不同的适配代码
8.Webpack 的 style-loader 和 css-loader 有什么区别
9.怎么使用 PostCSS 来处理 CSS
10.Webpack 中怎么给静态资源添加上 CDN 域名?
11.url-loader 和 file-loader 有什么区别?
12.怎么配置 Webpack 的多页面开发?