本文将继续引入更多的 webpack
配置,建议先阅读【webpack 系列】基础篇的内容。若是发现文中有任何错误,请在评论区指正。本文全部代码均可在 github 找到。css
以前咱们配置的是一个单页的应用,可是咱们的应用可能须要是个多页应用。下面咱们来进行多页应用的 webpack
配置。 先看一下咱们的目录结构html
├── public
│ ├── detail.html
│ └── index.html
├── src
│ ├── detail-entry.js
│ ├── index-entry.js
复制代码
public
下面有 index.html
和 detail.html
两个页面,对应 src
下面有 index-entry.js
和 detail-entry.js
两个入口文件。前端
在webpack.config.js
配置node
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
// ...
module.exports = {
entry: {
index: path.resolve(__dirname, 'src/index-entry.js'),
detail: path.resolve(__dirname, 'src/detail-entry.js')
},
output: {
path: path.resolve(__dirname, 'dist'), // 输出目录
filename: '[name].[hash:6].js', // 输出文件名
},
plugins: [
// index.html
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/index.html'), // 指定模板文件,不指定会生成默认的 index.html 文件
filename: 'index.html', // 打包后的文件名
chunks: ['index'] // 指定引入的 js 文件,对应在 entry 配置的 chunkName
}),
// detail.html
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/detail.html'), // 指定模板文件,不指定会生成默认的 index.html 文件
filename: 'detail.html', // 打包后的文件名
chunks: ['detail'] // 指定引入的 js 文件,对应在 entry 配置的 chunkName
}),
// 打包前自动清除dist目录
new CleanWebpackPlugin()
]
}
复制代码
npm run build
以后能够看到生成的 dist
目录以下webpack
dist
├── assets
│ └── author_ee489e.jpg
├── detail.dbcb15.js
├── detail.dbcb15.js.map
├── detail.html
├── index.dbcb15.js
├── index.dbcb15.js.map
└── index.html
复制代码
index.html
页面中已经引入了打包好的 index.dbcb15.js
文件,detail.html
文件也已经引入了 detail.dbcb15.js
文件。更多配置请查看 html-webpack-plugin。git
webpack4
对 css
模块支持的完善以及在处理 css
文件提取的方式上也作了些调整,由 mini-css-extract-plugin
来代替以前使用的 extract-text-webpack-plugin
,使用方式很简单。es6
该插件将 css
提取到单独的文件中,为每一个包含 css
的 js
文件建立一个 css
文件,支持 css
和 sourcemap
的按需加载。 与 extract-text-webpack-plugin
相比有以下优势github
css
安装 extract-text-webpack-plugin
web
npm i -D mini-css-extract-plugin
复制代码
配置 webpack.config.js
npm
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// ...
module.exports = {
// ...
module: {
rules: [
{
test: /\.(c|le)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'],
exclude: /node_modules/
},
{
test: /\.sass$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'],
exclude: /node_modules/
},
// ...
]
},
plugins: [
// ...
new MiniCssExtractPlugin({
filename: 'css/[name].[hash:6].css'
})
]
}
复制代码
npm run build
以后会发如今 dist/css
目录有了抽离出来的 css
文件了。
这时咱们发现两个问题:
css
文件没有进行压缩。hash
部分都是同样的,存在缓存问题。经过 optimize-css-assets-webpack-plugin
插件压缩 css
代码
npm i -D optimize-css-assets-webpack-plugin
复制代码
配置 webpack.config.js
// webpack.config.js
//...
const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
//...
plugins: [
//...
new OptimizeCssPlugin()
]
}
复制代码
这样就能够对 css
文件进行压缩了。
对于第二个问题,咱们首先须要了解下 hash
、chunkHash
、contentHash
的区别。
hash
是基于整个 module identifier
序列计算获得的,webpack 默认为给各个模块分配一个 id
以做标识,用来处理模块之间的依赖关系,默认的 id
命名规则是根据模块引入的顺序赋予一个整数(1
、2
、3
...)。任意修改、增长、删除一个模块的依赖,都会对整个 id
序列形成影响,从而改变 hash
值。也就是每次修改或者增删任何一个文件,全部文件名的 hash
值都将改变,整个项目的文件缓存都将失效。
output: {
path: path.resolve(__dirname, 'dist'), // 输出目录
filename: '[name].[hash:6].js', // 输出文件名
}
new MiniCssExtractPlugin({
filename: 'css/[name].[hash:6].css'
})
复制代码
能够看到打包后的 js
和 css
文件的 hash
值是同样的,因此对于没有发生改变的模块而言,这样作是不合理的。
固然能够看到,对于图片等资源该 hash
仍是能够生成一个惟一值的。
chunkhash
根据不一样的入口文件进行依赖文件解析、构建对应的 chunk
,生成对应的哈希值。咱们将 filename
配置成 chunkhash
来看一下打包的结果。
output: {
path: path.resolve(__dirname, 'dist'), // 输出目录
filename: '[name].[chunkhash:6].js', // 输出文件名
}
new MiniCssExtractPlugin({
filename: 'css/[name].[chunkhash:6].css'
})
复制代码
index.js
和
detail.js
的
chunkhash
是不同的。可是会发现
index.js
和
index.css
以及
detail.js
和
detail.css
的
chunkhash
是一致的,而且任意改动
js
或者
css
都会引发对应的
css
和
js
文件的
chunkhash
的改变,这是不合理的。因此这里抽离出来的
css
文件将使用
contenthash
,来区分
css
文件和
js
文件的更新。
contenthash
是针对文件内容级别的,只有你本身模块的内容变了,那么 hash
值才改变。
output: {
path: path.resolve(__dirname, 'dist'), // 输出目录
filename: '[name].[chunkhash:6].js', // 输出文件名
}
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:6].css'
})
复制代码
OK
,能够看到分离出来的
css
文件已经和入口文件的
hash
值区分开了。
为了实现理想的缓存,咱们通常这样使用他们:
JS
文件使用 chunkhash
CSS
样式文件使用 contenthash
gif|png|jpe?g|eot|woff|ttf|svg|pdf
等使用 hash
不少时候咱们并不须要在一个页面中一次性加载全部的 js
或者 css
文件,而是应该是须要用到时才去加载相应的 js
或者 css
文件。
好比,如今咱们须要点击一个按钮才会使用对应的 js
、css
文件,须要 import()
语法:
// index-entry.js
import './index.sass';
//...
const handle = () => import('./handle');
const handle2 = () => import('./handle2');
document.querySelector('#btn').onclick = () => {
handle().then(module => {
module.handleClick();
});
handle2().then(module => {
module.default();
});
}
复制代码
// handle.js
import './handle.css';
export function handleClick () {
console.log('handleClick');
}
复制代码
// handle2.js
export default function handleClick () {
console.log('handleClick2');
}
复制代码
npm run build
能够看到,多了这 3
个文件,而且只有在咱们点击该按钮是才会去加载这 3
个文件。
这些文件可能不太好区分,咱们能够经过设置 webpackChunkName
来定义生成的文件名
// index-entry.js
const handle = () => import(/* webpackChunkName: "handle" */ './handle');
const handle2 = () => import(/* webpackChunkName: "handle2" */ './handle2');
复制代码
咱们再将这些文件的 hash
长度设置为 8
加以区分
// webpack.config.js
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'), // 输出目录
filename: '[name].[chunkhash:6].js', // 输出文件名
chunkFilename: '[name].[chunkhash:8].js'
}
// ...
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:6].css',
chunkFilename: 'css/[name].[contenthash:8].css'
}),
复制代码
npm run build
以后查看
handle
和
handle2
文件的
webpackChunkName
设置成同样的,这样这两个文件将会打包在一块儿生成一个文件,能够减小请求数量。
开发过程当中,咱们但愿在浏览器不刷新页面的状况下可以去加载咱们修改的代码,来提升咱们的开发效率。咱们来看下如何配置:
webpack-dev-server
的热更新开关HotModuleReplacementPlugin
插件HotModuleReplacementPlugin
插件是 Webpack
自带的,在 webpack.config.js
直接配置
// webpack.config.js
module.exports = {
devServer: {
//...
hot: true
},
plugins: [
//...
new webpack.HotModuleReplacementPlugin() // 热更新插件
]
}
复制代码
在入口文件添加
if (module && module.hot) {
module.hot.accept()
}
复制代码
这样就完成了热更新的配置,可是此时 webpack
打包却报错了。
HotModuleReplacementPlugin
此时须要使用
hash
来输出文件,使用
chunkhash
会致使
webpack
报错,而生产环境则没有问题。可是如今咱们只是经过
process.env.NODE_ENV
这个变量来区分环境,这显然不是一个很好的方式。 咱们最好可以须要区分一下开发环境和生产环境的配置文件。
咱们能够给不一样的环境定义不一样的配置文件,可是这些文件将会有大量类似的配置,这时咱们能够这样来定义文件:
webpack.base.js
:定义公共的配置webpack.dev.js
:定义开发环境的配置webpack.prod.js
:定义生产环境的配置咱们能够将一些公共的配置抽离到 webpack.base.js
,而后在 webpack.dev.js
和 webpack.prod.js
进行对应环境的配置。咱们还须要经过 webpack-merge
来合并两个配置文件。
安装 webpack-merge
npm i -D webpack-merge
复制代码
如今 webpack.dev.js
就是这样的
// webpack.dev.js
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.config.base');
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: '9000', // 默认是8080
compress: true, // 是否启用 gzip 压缩
hot: true
},
output: {
path: path.resolve(__dirname, 'dist'), // 输出目录
filename: '[name].[hash:6].js', // 输出文件名
chunkFilename: '[name].[hash:8].js'
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[hash:6].css',
chunkFilename: 'css/[name].[hash:8].css'
}),
new webpack.HotModuleReplacementPlugin() // 热更新插件
]
});
复制代码
同时须要在 package.json
中指定咱们的配置文件
// package.json
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.dev.js",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.pro.js"
},
复制代码
这时咱们就很优雅的区分开不一样环境的配置了。
有时候咱们须要在 html
中直接引用一个打包好的第三方插件库,这个库不须要经过 webpack
编译。好比咱们 lib
目录下有个 lib-a.js
,须要在 public/index.html
中直接引用它。
<!-- public/index.html -->
<script src="/lib/lib-a.js"></script>
复制代码
这时 build
以后会发现 dist
下是没有 lib
目录的,这时会找不到这个文件。这时咱们须要借助 CopyWebpackPlugin
这个插件来帮助咱们把根目录下的 lib
目录拷贝到 dist
目录下面。
首先安装 CopyWebpackPlugin
npm i -D CopyWebpackPlugin
复制代码
配置 webpack.config.js
// webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
//...
plugins: [
//...
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, 'lib'),
to: path.resolve(__dirname, 'dist/lib')
}
])
]
}
复制代码
这时后运行 npm run build
就会发现,dist
目录下已经有了 lib
目录及文件了。
更多的配置请查看copy-webpack-plugin。
Webpack
在启动后会从配置的入口模块出发找出全部依赖的模块,Resolve
配置 Webpack
如何寻找模块所对应的文件。 Webpack
内置 JavaScript
模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你也能够根据本身的须要修改默认的规则。
resolve.alias
配置项经过别名来把原导入路径映射成一个新的导入路径。 好比咱们在 index-entry.js
中引入 lib/lib-b.js
,你可能须要这样引入
import '../lib/lib-b.js';
复制代码
而当目录层级比较深时,这个相对路径就会变得很差辨认了。这时咱们能够配置 lib
的一个别名。
// webpack.config.js
module.exports = {
//...
resolve: {
alias: {
'@lib': path.resolve(__dirname, 'lib') // 为lib目录添加别名
}
}
}
复制代码
这时不管你处于目录的哪一个层级,你只须要这样引入
import '@lib/lib-b.js';
复制代码
若是在导入文件时没有带后缀名,webpack
会自动带上后缀后去尝试访问文件是否存在。 resolve.extensions
用于配置在尝试过程当中用到的后缀列表,默认是
extensions: ['.js', '.json']
复制代码
就是说当遇到 import '@lib/lib-b';
时,webpack
会先去寻找 @lib/lib-b.js
文件,若是该文件不存在就去寻找 @lib/lib-b.json
文件, 若是仍是找不到就报错。
若是你想优先使用其余后缀文件,好比 .ts
文件,能够这样配置
// webpack.config.js
module.exports = {
//...
resolve: {
alias: {
'@lib': path.resolve(__dirname, 'lib'), // 为lib目录添加别名
extensions: ['.ts', '.js', '.json'] // 从左往右
}
}
}
复制代码
这样就会先去找 .ts
了。不过通常咱们会将高频的后缀放在前面,而且数组不要太长,减小尝试次数,否则会影响打包速度。
如今咱们引入 js
文件时能够省略后缀名了。
resolve.modules
配置 webpack
去哪些目录下寻找第三方模块,默认是只会去 node_modules
目录下寻找。若是项目中某个文件夹下的模块常常被导入,不但愿写很长的路径,好比 import '../../../components/link'
,那么就能够经过配置 resolve.modules
来简化。
// webpack.config.js
module.exports = {
//...
resolve: {
modules: ['./src/components', 'node_modules'] // 从左到右查找
}
}
复制代码
这时,你就能够经过 import 'link'
引入了。
有一些第三方模块会针对不一样环境提供几份代码。例如分别提供采用 es5
和 es6
的 2
份代码,这 2
份代码的位置写在 package.json
文件里。
{
"jsnext:main": "es/index.js",// 采用 ES6 语法的代码入口文件
"main": "lib/index.js" // 采用 ES5 语法的代码入口文件
}
复制代码
webpack
会根据 mainFields
的配置去决定优先采用那份代码, mainFields
默认配置以下:
mainFields: ['browser', 'main']
复制代码
假如你想优先采用 ES6
的那份代码,能够这样配置:
mainFields: ['jsnext:main', 'browser', 'main']
复制代码
resolve.enforceExtension
若是配置为 true
,那么全部导入语句都必需要带文件后缀。
enforceModuleExtension
和 enforceExtension
做用相似,但 enforceModuleExtension
只对 node_modules
下的模块生效。 由于安装的第三方模块中大多数导入语句没带文件后缀,若是这时你配置了 enforceExtension
为 true
,那么就须要配置 enforceModuleExtension: false
来兼容第三方模块。
本地开发时,前端项目的端口号是 9000
,可是服务端多是 9001
,根据浏览器的同源策略,是不能直接请求到后端服务的。固然你能够在后端配置 CORS
相关的头部来实现跨域,其实也能够经过 webpack
的配置来解决跨域问题。
首先,咱们起一个后端服务,安装 koa
、koa-router
npm i -D koa koa-router
复制代码
新建 server/index.js
// server/index.js
const Koa = require('koa');
const KoaRouter = require('koa-router');
const app = new Koa();
// 建立 router 实例对象
const router = new KoaRouter();
// 注册路由
router.get('/user', async (ctx, next) => {
ctx.body = {
code: 0,
data: {
name: '阿林十一'
},
msg: 'success'
};
});
app.use(router.routes()); // 添加路由中间件
app.use(router.allowedMethods()); // 对请求进行一些限制处理
app.listen(9001);
复制代码
使用 node server/index.js
启动服务后,在 http://localhost:9001/user
能够访问结果。
以后再修改 handle.js
,在点击按钮以后会请求接口
import './handle.css';
export function handleClick () {
console.log('handleClick');
fetch('/api/user')
.then(r => r.json())
.then(data => console.log(data))
.catch(err => console.log(err));
}
复制代码
这是会发现接口报 404
,下面咱们配置一下 webpack.config.dev.js
// webpack.config.dev.js
module.exports = {
//...
proxy: {
'/api': {
target: 'http://127.0.0.1:9001/',
pathRewrite: {
'^/api': ''
}
}
}
}
复制代码
请求到 http://localhost:9000/api/user
如今会被代理到请求 http://localhost:9001/user
。点击按钮发起请求:
如今,咱们对 webpack
的配置有了更进一步的了解了,快动手试试吧。本文全部代码能够查看 github。
后续将会继续推出 webpack
系列的其余内容哦~
喜欢本文的话点个赞吧~
更多精彩内容,欢迎关注微信公众号~