前言
下载啥的就不多说了,就看看我们项目中经常用到的一些配置。码字不易,喜欢的话点个?哦 ~~~
webpack
webpack只是一个打包模块的机制,只是把依赖的模块转化成可以代表这些包的静态文件。webpack就是识别你的 入口文件。识别你的模块依赖,来打包你的代码。至于你的代码使用的是commonjs还是amd或者es6的import。webpack都会对其进行分析。来获取代码的依赖。webpack做的就是分析代码。转换代码,编译代码,输出代码。webpack本身是一个node的模块,所以webpack.config.js是以commonjs形式书写的(node中的模块化是commonjs规范的) webpack中每个模块有一个唯一的id,是从0开始递增的。整个打包后的bundle.js是一个匿名函数自执行。参数则为一个数组。数组的每一项都为个function。function的内容则为每个模块的内容,并按照require的顺序排列。
clean-webpack-plugin
清空打包文件。
new CleanWebpackPlugin()
, 可以不传参,配置参考这里
source map
sourcemap就是一个信息文件,里面储存着位置信息。目的是为了解决开发代码与实际运行代码不一致时帮助我们debug到原始开发代码的技术。
- 配置 devtool 属性 ( 最佳实践 )
- development:
cheap-module-eval-source-map
- production:
cheap-module-source-map
- 五个关键字任意组合: eval,source-map,cheap,module,inline
- eval: 包裹模块,在模块尾添加模块来源 //#sourceURL,通过 sourceURL 找到原始代码位置,不单独产生.map 文件
- source-map: 产生.map文件,包含原始代码和运行代码的映射关系
- 参考文献
webpack-dev-server
npm i webpack-dev-server -D
- 注意:打包的文件不会放到dist目录中了,而是放在我们的内存中,从而提升了打包速度。
devServer: {
// open: true, // 打开浏览器
// port: 8080, // default
hot: true, // HMR,不刷新页面就能应用你改过的css样式
hotOnly: true, // 如果HMR没生效,也不刷新页面
contentBase: './dist', // 告诉服务器从哪里提供内容。只有在您希望提供静态文件时才需要这样做。
proxy: {
'/api': 'http://localhost:3000' // 如果我们访问localhost:8000/api ,则转发请求到localhost:3000
}
},
复制代码
- 通过配置hot: true 和 webpack.HotModuleReplacementPlugin() 可以及时更新css样式而不刷新页面!
- 如果更变js代码,保证其它代码的状态不发生变化,则需要另外加一段代码,如下:
+ if (module.hot) {
+ module.hot.accept('./print.js', function() {
+ // 检测到print.js中的更改时,我们告诉webpack接受更新后的模块。
+ console.log('Accepting the updated printMe module!');
+ printMe();
+ })
+ }
复制代码
- 注意: 为什么改变css代码不需要添加 module.hot.accpet 代码呢? 原因是因为 css-loader 已经帮我们处理过这一步了,js 代码就需要我们自己来添加 HMR 了。在使用 vue 的时候,vue-loader 帮我们实现了 js HMR 这一块了,所以也不用我们自己实现了。react 则是借助了 babel-preset 来帮我们实现了 js HMR.
webpack-dev-middleware
通过webpack-dev-middleware 配合 express 可以自己搭建一个简单的webpack-dev-server,通过node来运行webpack,代码如下:
server.js
const webpack = require('webpack')
const middleware = require('webpack-dev-middleware')
const express = require('express')
const config = require('./webpack.config.js')
// webpack 编译器
const compiler = webpack(config)
const app = express()
app.use(
middleware(compiler, {
// webpack-dev-middleware options
publicPath: config.output.publicPath
})
)
app.listen(3000, () => {
console.log('Example app listening on port 3000!')
})
复制代码
使用babel处理ES6
进入官网,打开 setup, 进入webpack,查看相关文档
// npm install --save-dev babel-loader @babel/core
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // 不包含
loader: "babel-loader" // webpack 和 babel 做通信的桥梁
}
]
}
// 还需要配置options 或者 .babelrc 文件
npm install @babel/preset-env --save-dev
{
test: /\.js$/,
exclude: /node_modules/, // 不包含
loader: "babel-loader", // webpack 和 babel 做通信的桥梁
options: {
"presets": ["@babel/preset-env"]
}
}
// 但是,这只是将ES6 转 ES5,有一些语法比如promiss,map等,低版本还是不认识,这就要使用 @babel/polyfill 了
复制代码
- @babel/polyfill
// 提供polyfill是为了方便,但是您应该将它与@babel/preset-env和useBuiltIns选项一起使用
// 这样它就不会包含并非总是需要的整个polyfill。否则,我们建议您手动导入各个填充。
npm install --save @babel/polyfill
// 然后在代码的顶部引入:
import "@babel/polyfill";
"presets": [["@babel/preset-env"], {
targets: {
chrome: "67", // chrome 版本大于67的,就不需要将ES6转ES5了,因为chrome对ES6已经支持的很好了
"ie": "11"
},
useBuildIns: "usage" // useBuiltIns选项,如果设置成"usage",那么将会自动检测语法帮你require你代码中使用到的功能。也不需要额外引入@babel/polyfill 了
}]
复制代码
通过 .babelrc 来声明 参考文档
- @babel/plugin-transform-runtime
参考文献
- 避免多次编译出helper函数:
- 这里的 @babel/runtime 包就声明了所有需要用到的帮助函数,而 @babel/plugin-transform-runtime 的作用就是将所有需要helper函数的文件,依赖@babel/runtime包
- 解决@babel/polyfill提供的类或者实例方法污染全局作用域的情况。
// npm install --save-dev @babel/plugin-transform-runtime
// npm install --save @babel/runtime-corejs2
.babelrc
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2, // 代表需要使用corejs的版本; npm install --save @babel/runtime-corejs2
"helpers": true,
"regenerator": true,
"useESModules": false // 按需引入babel/polyfill (注入低版本的polyfill)
}
]
]
}
复制代码
webpack打包React
安装包 npm i @babel/preset-env @babel/preset-react -D
tree shaking
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 import 和 export。不支持 require 的引入方式。这是因为 ES 模块的引入方式是静态的,而require 的引入方式是动态的。
- 配置
- 将 mode 配置选项设置为 development 以确保 bundle 是未压缩版本
mode: "development",
optimization: {
usedExports: true // 查看那些模块被使用了,使用了的就打包
}
// 还要再 package.json 中配置一个属性
"sideEffects": false
// "side effect(副作用)" 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
"sideEffects": ["@babel/polyfill", "*.css"] // 不对 @babel/polyfill和所有引入的css文件 作tree shaking
复制代码
注意: 在开发环境下,使用tree shaking 其实还是会将没有使用的模块打包进 bundle.js 中,只不过会提醒你那些模块没有使用。如果使用 mode: production; 那我们就不需要使用 optimization 配置项了,并且将设置 devtool: cheap-module-source-map。但是 sideEffects 还是要使用。
区分打包(dev and prod)
npm i webpack-merge -D 用来合并 webpack 模块
- webpack.common.js,存放 dev 和 prod 公共的配置
- webpack.dev.js, 开发环境的配置
- webpack.prod.js,生产环境的配置
- 我们可以将以上的webpack配置文件放入到 build 文件夹中统一管理
看看 webpack.prod.js 的用法
webpack.prod.js
const merge = require('webpack-merge')
const devConfig = require('webpack.common.js')
cosnt prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map'
}
module.exports = merge(prodConfig, merge)
复制代码
然后修改 package.json 中的配置
// 启动dev
"dev": "webpack-dev-server --config ./build/webpack.dev.js"
// 启动prod
"build": "webpack --config ./build/webpack.prod.js"
复制代码
注意: 如果我们的webpack配置放在build文件夹中,并且我们的配置中使用 clean-webpack-plugin,那么它的配置也需要发生改变
new CleanWebpackPlugin(['dist']) // 指的是删除当前目录下的 dist 目录,但是我们的 dist 目录需要放在 build 文件夹同级目录下。
// 可以这样做 : 在 github 上搜索 clean-webpack-plugin
new CleanWebpack(['dist'], {
root: path.resolve(__dirname, '../') // 指定根路径
})
复制代码
Code Splitting (代码分割)
代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
常用的代码分离方法有三种:
-
入口起点:使用 entry 配置手动地分离代码。(优点:最简单最直观。缺点:对我们的示例来说毫无疑问是个严重问题,因为我们在 ./src/index.js 中也引入过 lodash,这样就造成在两个 bundle 中重复引用。)
-
防止重复:使用 SplitChunksPlugin 去重和分离 chunk。
optimization: { // 这里可以配置 code splitting, 还可以配置 tree shaking 时需要的 usedExports
splitChunks: {
chunks: 'all'
}
}
复制代码
将index.js 和 another_module.js 中的 lodash 库抽离出来了。
- 动态导入:
通过模块中的内联函数调用来分离代码。 我们不再使用 statically import(静态导入) lodash,而是通过 dynamic import(动态导入) 来分离出一个 chunk。 查看官网demo
环境变量
想要消除 开发环境 和 生产环境 之间的 webpack.config.js 差异,你可能需要环境变量(environment variable)。***参考文档***
- 配置
- 对于我们的 webpack 配置,有一个必须要修改之处。通常,module.exports 指向配置对象。要使用 env 变量,你必须将 module.exports 转换成一个函数:
const path = require('path');
module.exports = env => {
// Use env. here:
console.log('NODE_ENV: ', env.NODE_ENV); // 'local'
console.log('Production: ', env.production); // true
return {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
};
复制代码
- 既然可以在配置文件中接收到 env ,那么我们就可以将上面的 mode 区分部分重新修改一下,怎么修改呢?如下:
// 在上面的 环境区分中,我们在 webpack.common.js 中引入 devConfig、prodConfig、webpack-merge,然后通过 env 来判断当前的打包命令是开发环境还是生产环境。
module.exports = env => {
if (env && env.production) {
return merge(commonConfig, prodConfig);
} else {
return merge(commonConfig, devConfig);
}
}
复制代码
Library 打包
除了打包应用程序,webpack 还可以用于打包 JavaScript library. 参考文档
- libraryTarget 还可以赋值为 'this' 或者 'window' | 'global',意味着将 library 挂载到 全局上
- 如果我们自己写的库中引入的第三方库,比如lodash.js,但是我们不希望它打包到我们的库中,那么应该怎么办呢?我们需要配置如下参数
// webpack.config.js
externals: ['lodash']
// 不打包lodash,但是别人引入我们的库的时候,就必须引入lodash.js,因为我们的库依赖lodash
复制代码
- 发布自己的库到 npm 上,给别人使用
- 首先配置我们的package.json文件
- 到 npm 官网上注册我们的账号
- npm adduser (添加用户名和密码)
- npm publish (将我们自己的库发布到 npm 仓库上去)
- 注意,库的名字一定要特别,不能和npm 仓库上的库同名
渐进式网络应用程序 ( PWA )
我们通过搭建一个简易 server 下,测试下这种离线体验。这里使用 http-server package:npm install http-server --save-dev 参考
{
"scripts": {
+ "build": "webpack",
+ "start": "http-server dist"
}
}
复制代码
- 添加 Workbox
npm install workbox-webpack-plugin --save-dev
const WorkboxPlugin = require('workbox-webpack-plugin');
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
+ title: '渐进式网络应用程序'
+ }),
+ new WorkboxPlugin.GenerateSW({
+ // 这些选项帮助快速启用 ServiceWorkers
+ // 不允许遗留任何“旧的” ServiceWorkers
+ clientsClaim: true,
+ skipWaiting: true
+ })
]
复制代码
- 注册 Service Worker
接下来我们注册 Service Worker,使其出场并开始表演。通过添加以下注册代码来完成此操作:
import _ from 'lodash';
import printMe from './print.js';
+ if ('serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker.register('/service-worker.js').then(registration => {
+ console.log('SW registered: ', registration);
+ }).catch(registrationError => {
+ console.log('SW registration failed: ', registrationError);
+ });
+ });
+ }
复制代码
TypeScript
TypeScript 是 JavaScript 的超集,为其增加了类型系统,可以编译为普通 JavaScript 代码 参考
npm install --save-dev typescript ts-loader
复制代码
- 在webpack.config.js中配置
- 需要创建一个tsconfig.json
{
"compilerOptions": {
"outDir": "./dist/", // 当前目录下的dist
"noImplicitAny": true, //
"module": "es6", // 使用ES Module 引入方式
"target": "es5", // 转换成 ES5 的代码
"jsx": "react",
"allowJs": true // 允许引入 js 文件
}
}
复制代码
- 使用typescript时我们可能使用外部的库,但是外部的库是无法利用typescript自动检测的特性的。这个时候我们就需要安装这些库的类型文件,比如lodash, 我们如何安装它的自动检测类型文件呢?
npm i @types/lodash -D // 通过 @types/ + 库的名字即可
复制代码
devServer.proxy
// 请求到 /api/users 现在会被代理到请求 http://localhost:3000/api/users
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {
'header.json': 'demo.json' //我们请求header.json的时候,转而去请求demo.json
},
secure: false, // 默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。如果你想要接受,修改配置
bypass: function(req, res, proxyOptions) { // 拦截,如果请求的是 html 类型的数据,就直接返回 html文件,不进行转发
if (req.headers.accept.indexOf("html") !== -1) {
console.log("Skipping proxy for browser request.");
return "/index.html";
}
}
}
}
}
复制代码
devServer.historyApiFallback
当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html.在做单页面路由的时候,需要配置此选项。
devServer: {
historyApiFallback: true
}
复制代码
ESlint配置
约束代码规范的工具,团队开发尤其重要
// 安装
npm i eslint -D
// 初始化配置文件
npx eslint --init
复制代码
- 使用Airbnb公司的规范,我们需要另外覆盖一些配置
- 在webpack中配置 eslint-loader
注意: 在webpack.devServer中配置一个属性:npm i eslint-loader -D (会降低打包的速度哦 ! 可以在options中配置 cache 属性) 参考官网配置
devServer: {
overlay: true, // 将报错显示在页面上
}
复制代码
webpack性能提升
- 升级版本
- 比如webpack,node,npm,yarn
- 尽量少使用 Loader
- 在使用 babel-loader编译 js 代码的时候,使用 exclude 或者 include来忽略掉 node_modules下的文件,因为这个文件下的文件都是编译过了的,就没必要再次让 babel-loader 来编译了。
- 尽量少使用 Plugin
- 比如在开发环境中,我们就没必要使用压缩css或者js 代码了。
- 合理配置resolve
- resolve.alias: 创建 import 或 require 的别名,来确保模块引入变得更简单
- resolve.extensions: 自动解析确定的扩展。默认值为:extensions: [".js", ".json"], 能够使用户在引入模块时不带扩展:import File from '../path/to/file'.也可以添加 'jsx' 之类的文件
- resolve.mainFields: 通过配置这个选项,来引入可以像省略 index.js 这样的文件。比如我们引入
import index from './src'
,其实就是引入的src下的index.js,我们也可以配置其它的文件,比如 main.js,hello.js ...
- DllPlugin
DLLPlugin 和 DLLReferencePlugin 用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。
- 实现的逻辑是,将第三方库只打包一次并打包成一个文件 (也可以打包成多个文件,配置 entry 属性就行了),然后将其缓存起来,之后再做打包的时候,不再去node_modules中寻找了
- 创建一个 webpack.dll.js 配置文件
将我们打包好的第三方库源代码引入到 HtmlWebpackPlugin 生成的 index.html 中
- 使用 webpack.DllRefrencePlugin 来分析代码
这个插件是在 webpack 主配置文件(webpack.common.js)中设置的。它会结合manifest.json、第三方库源码文件以及我们引入的第三方库文件做一个分析,如果它发现我们引入的文件在源码文件里面已经有了,它就直接拿过来用了,而不会node_modules里面找了。
new webpack.DllRefrencePlugin({
manifest: path.resolve(__dirname, './vendors.manifest.json')
})
复制代码
- 拆分成多个第三方库文件
- 控制包文件大小
- 还记得 tree shaking 吧?去除冗余代码 ...
- splitChunks 来将大文件拆分成多个小文件
- 多进程打包
webpack 默认是使用 node.js 来运行,即采用的单线程机制打包过程
- node里面的多进程 thread-loader, parallel-webpack, happypack
- 合理使用 sourceMap
描述越详细,就越慢哦 ~
- 结合 stats 分析打包结果
结合分析,查看哪个模块打包耗时比较长,做针对性处理
- 开发环境内存编译
采用 webpack-dev-server
- 开发环境剔除无用插件
比如压缩 css 或者 js 的插件
- 这里我们需要配置环境变量来区分打包、
多页面打包配置
本质就是创建多个entry 以及 HtmlWebpackPlugin 来实现的
- 比如我们要打包两个js文件,并且通过两个 index.html 分别引入
如何编写 Loader
其实 loader 就是一些函数(不能使用箭头函数哦,因为this),接受的参数就我们的源代码,通过函数来做一些处理并返回而已。loaderAPI
// myself-loader.js
module.exports = function (source) {
return something ...
}
// 然后在 module 中配置
module: {
rules: [
{
test: /\.js$/,
loader: path.resolve(__dirname, './loaders/myself-loader.js')
}
]
}
// 如何传参?
// 配置 options 之后,在我们写的 laoder 里面通过 this.query 就能获取到啦 !!!
{
test: /\.js$/,
use: [
loader: path.resolve(__dirname, './loaders/myself-loader.js'),
options: {
name: 'alex.cheng'
}
]
}
// 自己定义的 loader 如何像引入 node_modules 里面 loader 一样引入呢 ?
resolveLoader: {
modules: ["node_modules", "./loader"] // 如果在node_modules里没找到,就到当前文件loader中去找
}
复制代码
如何编写一个Plugin
我们使用别人的 plugin 的时候都是怎么使用的呢? 是不是都要 new Plugin ? 所以啊,我们的 plugin 都是通过构造函数来编写的。来看一个简单的例子
// plugin: alex-cheng-webpack-plugin.js
class AlexChengWebpackPlugin {
constructor() {
console.log('alex.cheng plugin is excuted !')
}
}
module.exports = AlexChengWebpackPlugin
// webpack.config.js
const path = require('path')
const AlexChengWebpackPlugin = require('./src/myPlugins/alex-cheng-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: '[name].js',
path: path.resolve(__dirname, './dist/')
},
plugins: [
new AlexChengWebpackPlugin() // 调用就行了啦 !!
]
}
复制代码
看!我们自己的插件就执行了咯 ! 详情可以参考 官网API