单页路由在devServer中的配置
一般来说,使用了主流前端框架的项目,如vue和react,生成的项目都是单页应用,使用前端路由来切换页面的展示
import React, { Component } from "react";
import { BrowserRouter, Route } from "react-router-dom";
function Home(props) {
return <div>Home</div>;
}
function Demo(props) {
return <div>Demo</div>;
}
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Route path="/demo" component={Demo}></Route>
<Route path="/" exact component={Home}></Route>
</div>
</BrowserRouter>
);
}
}
export default App;
这个页面乍一看是没什么问题的,我们也可以将它运行起来,Home也正常显示了
但这时我们切换到Demo页,会发现出问题了
问题的原因在于当你切换至demo的时候,浏览器会去找demo.html的文件,而不是继续展示index.html下的demo部分,而单页应用只会有一个html文件,浏览器找不到demo.html,所以报错
解决这个问题,需要在webpack的devServer中做一些配置
// webpack.config.js
devServer: {
// 告诉devServer在哪里查找文件
contentBase: "../myDist",
// 启动后自动浏览器打开
open: true,
// 端口
port: 4396,
hot: true,
// 设置为true后,所有404的页面请求会被替代为index.html
historyApiFallback: true
},
配置之后,可以看到demo页可以正常显示了
除了布尔值,也可以传入一个对象来做进一步的配置
// webpack.config.js
devServer: {
// 告诉devServer在哪里查找文件
contentBase: "../myDist",
// 启动后自动浏览器打开
open: true,
// 端口
port: 4396,
hot: true,
historyApiFallback: {
rewrites: [
// 单独设置demo页面的跳转规则
{ from: /demo.html/, to: '/xxx.html' },
// 等同于historyApiFallback:true
{from: /\.*/, to: '/index.html'}
]
}
},
但通常来说,配置一个true对于正常的单页应用就足够了
EsLint的配置
有时候,为了团队开发代码风格的统一,我们可能需要在项目中配置并使用eslint来进行代码的规范,而webpack也提供了一个对应的loader来进行支持
首先安装eslint
npm i eslint -D
安装之后运行npx eslint -init来生成一个配置文件,他会提供一些问题供你选择来生成相应的配置文件
这是我的选择,可以根据项目中实际的情况来进行变更
安装完成之后,我们可以运行npx eslint src 来检查src下的代码在当前eslint规则下是否有错误
不出意外的报了一堆错
如果我们没有用webpack,那么到这里就该去改代码了,但是我们使用了webpack,正确的流程应该是在webpack打包的时候,自动帮我们自动进行eslint的一些检查,有错误的话弹出错误提示,这时候就需要对webpack进行一些配置了
在官方文档上,推荐使用的是eslint-loader,但是当我们进入eslint-loader的github页面时却提示已废弃,建议使用eslint-webpack-plugin,不得不吐槽webpack官方文档的更新效率…
安装一下该插件
npm install eslint-webpack-plugin --save-dev
安装之后在配置文件中使用即可
// webpack.config.js
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports={
...
plugins:[
new ESLintPlugin()
]
}
再次启动devServer会发现在启动过程中报错了,但是项目还是可以正常运行起来的,如果不去看命令行可能就发现不了这些错误
如果想要强势一些,不修改错误就无法运行的话,可以在devServer的配置里加一条overlay:true,这样遇到报错的话,就会展示一个错误页面
devServer: {
// 如果编译中出错,则弹出错误页面
overlay:true,
// 告诉devServer在哪里查找文件
contentBase: "../myDist",
// 启动后自动浏览器打开
open: true,
// 端口
port: 4396,
hot: true,
},
开启了overlay之后,再运行devServer,一股熟悉的画风扑面而来…
这样就在我们的webpack中,开启了对eslint的支持
不过好像很多人更关心的是怎么从官方的脚手架下把启用的eslint禁用掉吧哈哈
webpack性能优化
webpack的打包性能优化也是一个老生常谈的问题了,方法有很多,我这里列举一些个人的理解,如果你有更好的方法也欢迎一起交流
跟上技术的迭代 (更新你的node,webpack,npm,yarn的版本)
webpack在迭代时,肯定会使打包的性能得到一定的提升,而webpack又是基于node的,所以确保node,webpack处于最新的版本肯定会使你的打包性能得到一定的提升
各种模块直接的适配则是使用诸如npm和yarn这类的包管理工具,更新这些包管理工具到最新的版本肯定也会使你的打包性能得到一定的提升
缩小loader的作用范围
在使用loader时候,我们应该使用include和exclude属性来确保loader只作用于他们该作用的地方,比如babel-loader,我们就应该配置只作用于我们业务代码的js文件,而不是node_modules文件夹下的各种第三方包的js文件
合理的控制loader的作用范围也会使打包的性能得到一定的提升
{
test: /\.(js|jsx)$/,
// 只对node_modules文件夹外的代码起作用
exclude: /node_modules/,
// 还可以使用include
// include: path.resolve(__dirname, "../src") 只对src目录下的业务代码起作用
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
},
],
"@babel/preset-react",
],
},
},
plugin尽可能精简并确保可靠
在使用plugin的时候,要思考这个plugin在当前的环境下是否需要,比如压缩css文件的optimize-css-assets-webpack-plugin,这个插件在生产环境下有助于压缩代码的体积,是可以使用的一个插件,但是在开发环境中,这个插件似乎就没有存在的意义了,就没有必要在开发环境的配置中使用该插件。
同样的还有clean-webpack-plugin,在开发环境运行devServer的时候,代码是运行在内存中的,没有必要在运行devServer的时候把我们的dist目录清空,所以这个插件在开发环境的配置中也是可以去掉的。
还要注意在使用插件时候,尽量选择在webpack官网中介绍的插件,这类的插件通常性能会比较好,也会更加的可靠,其他的第三方插件在性能或者功能上可能或多或少会有一些差距。
合理的选择并使用plugin也会对我们打包的性能有一定的提升
DllPlugin的使用
在开发中,我们肯定会引入一些第三方的库,而这些第三方的库的内容实际上是不会变更的,但我们现在的打包会在每次打包时候重新解析这些内容并重新将他们从node_modules中打包到我们的项目里,这样肯定会对性能有一定的影响,理想的情况应该是我们将这些第三方模块打包至单独的文件中,后续打包不再对这些文件做处理,直接使用即可,对此,webpack也有一个插件供我们使用
// index.js
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
ReactDom.render(<App/>, document.getElementById('root'))
在这里,我们使用了react和react-dom 2个第三方的模块,我们要新建一个专门打包dll文件的webpack配置文件用来打包dll并使用插件来生成一个manifest.json作为映射关系
// webpack.dll.js
const path = require("path");
const webpack = require("webpack");
module.exports = {
mode: "production",
entry: {
// 需要打包的第三方库
vendors: ["react", "react-dom"],
},
output: {
filename: "[name].dll.js",
path: path.resolve(__dirname, "../dll/"),
// 以库的方式打包,打包的内容会以全局变量形式暴露出去
library: "[name]",
},
plugins: [
// 使用插件对打包的代码进行分析,生成一个json文件作为映射关系
new webpack.DllPlugin({
name: "[name]",
path: path.resolve(__dirname, "../dll/manifest.json"),
}),
],
};
在package.json中加一条打包dll的命令
"dll":"webpack --config ./config/webpack.dll.js"
运行,打包dll文件,打包后应该是这样的
现在,我们已经单独打包了第三方的库,但是在项目中还没有使用,要在项目的webpack配置中做一些配置
首先要安装一个add-asset-html-webpack-plugin
npm i add-asset-html-webpack-plugin -D
然后在配置内做一些修改
// webpack.config.js
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin");
module.exports = {
....
plugins: [
new HtmlWebpackPlugin({
// 自定义title
title: "html模板",
// 自定义文件名
filename: "index.html",
// 自定义使用模板的路径
template: path.resolve(__dirname, "../src/public/index.html"),
}),
// 向html模板注入额外的文件,要将刚刚打包的dll文件注入到页面中
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, "../dll/vendors.dll.js"),
}),
// 读取映射关系,发现import的模块已经在dll中被打包了的话,就不再额外引入
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, "../dll/manifest.json"),
}),
],
}
这样经过配置后,再次打包,就可以达到将第三方模块整理至一个文件内,不做重复解析的效果了,这样会对后续项目的打包性能起到优化的作用
happypack
由于运行在 Node.js 之上的 Webpack 是单线程模型的,所以Webpack 需要处理的事情需要一件一件的做,不能多件事一起做。
我们需要Webpack 能同一时间处理多个任务,发挥多核 CPU 电脑的威力,就需要使用happypack。happypack实际上是在Webpack和Loader之间多加了一层,改成了Webpack并不是直接去和某个Loader进行工作,而是Webpack test到了需要编译的某个类型的资源模块后,将该资源的处理任务交给了HappyPack,由HappyPack在内部线程池中进行任务调度,分配一个线程调用处理该类型资源的Loader来处理这个资源,完成后上报处理结果,最后HappyPack把处理结果返回给Webpack,最后由Webpack输出到目的路径。
首先安装happypack
npm install --save-dev happypack
然后修改webpack的配置文件
// webpack.config.js
const HappyPack = require("happypack");
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
...
module:{
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
// 使用happypack的loader
use:'happypack/loader?id=js'
/*
这些配置移动至happypack的配置里
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
// 只将用到的es6语法整合进去,没有用到的不整合,减少打包后的大小
useBuiltIns: "usage",
},
]
],
},
*/
},
]
},
plugins:[
new HappyPack({
// 对应的id,用于匹配
id: "js",
// 共享进程池
threadPool: happyThreadPool,
// 是否打印日志
verbose: true,
// 如何处理,和loader配置一样,options变成query
loaders: [
{
loader: "babel-loader",
query: {
presets: [
[
"@babel/preset-env",
{
// 只将用到的es6语法整合进去,没有用到的不整合,减少打包后的大小
useBuiltIns: "usage",
},
]
],
},
},
],
}),
]
}
配置使用happypack在一些小项目中可能效果不明显,但是在一些大型的项目中,效果就会比较显著了
这是作者给出的一个使用happypack和dll前后打包时间的对比
在日后的大型项目中,可以考虑使用happypack来优化我们打包的性能
合理配置sourceMap
越详细的sourceMap对打包性能的影响也就越大,我们在不同的环境下应该使用不同的sourceMap选项,开发环境可能需要尽量详细的sourceMap帮我们定位错误,但是生产环境的代码则应尽量使用简洁的sourceMap或者直接不使用,从而提升我们打包的性能
控制包文件大小
项目中一些无用第三方模块的剔除,ui框架的按需引入,配置treeshaking等,都会减少我们代码的大小,从而起到优化打包速度的效果