快捷搜索:  汽车  科技

webpack 详细教程:带你深度解读Webpack系列 进阶篇

webpack 详细教程:带你深度解读Webpack系列 进阶篇//webpack.config.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { index: './src/index.js' login: './src/login.js' } output: { path: path.resolve(__dirname 'dist') filename: '[name].[hash:6].js' } //... plugins: [

webpack 遇到 import(****) 这样的语法的时候,会这样处理:

  • 因为入口新生成一个 Chunk
  • 当代码执行到 import 所在的语句时,才会加载该 Chunk 所对应的文件(如这里的1.bundle.8bf4dc.js)

大家可以在浏览器中的控制台中,在 Network 的 Tab页 查看文件加载的情况,只有点击之后,才会加载对应的 JS。

5.热更新


  1. 首先配置 devServer 的 hot 为 true
  2. 并且在 plugins 中增加 new webpack.HotModuleReplacementPlugin()

//webpack.config.js const webpack = require('webpack'); module.exports = { //.... devServer: { hot: true } plugins: [ new webpack.HotModuleReplacementPlugin() //热更新插件 ] }


我们配置了 HotModuleReplacementPlugin 之后,会发现,此时我们修改代码,仍然是整个页面都会刷新。不希望整个页面都刷新,还需要修改入口文件:

  1. 在入口文件中新增:

if(module && module.hot) { module.hot.accept() }

此时,再修改代码,不会造成整个页面的刷新。

webpack 详细教程:带你深度解读Webpack系列 进阶篇(1)

6.多页应用打包


有时,我们的应用不一定是一个单页应用,而是一个多页应用,那么如何使用 webpack 进行打包呢。为了生成目录看起来清晰,不生成单独的 map 文件。

//webpack.config.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { index: './src/index.js' login: './src/login.js' } output: { path: path.resolve(__dirname 'dist') filename: '[name].[hash:6].js' } //... plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' filename: 'index.html' //打包后的文件名 }) new HtmlWebpackPlugin({ template: './public/login.html' filename: 'login.html' //打包后的文件名 }) ] }

如果需要配置多个 HtmlWebpackPlugin,那么 filename 字段不可缺省,否则默认生成的都是 index.html,如果你希望 html 的文件名中也带有 hash,那么直接修改 fliename 字段即可,例如: filename: 'login.[hash:6].html'

生成目录如下:

. ├── dist │ ├── 2.463ccf.js │ ├── assets │ │ └── thor_e09b5c.jpeg │ ├── css │ │ ├── index.css │ │ └── login.css │ ├── index.463ccf.js │ ├── index.html │ ├── js │ │ └── base.js │ ├── login.463ccf.js │ └── login.html

看起来,似乎是OK了,不过呢,查看 index.htmllogin.html 会发现,都同时引入了 index.f7d21a.js login.f7d21a.js,通常这不是我们想要的,我们希望,index.html 中只引入 index.f7d21a.jslogin.html 只引入 login.f7d21a.js

HtmlWebpackPlugin 提供了一个 chunks 的参数,可以接受一个数组,配置此参数仅会将数组中指定的js引入到html文件中,此外,如果你需要引入多个JS文件,仅有少数不想引入,还可以指定 excludeChunks 参数,它接受一个数组。

//webpack.config.js module.exports = { //... plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' filename: 'index.html' //打包后的文件名 chunks: ['index'] }) new HtmlWebpackPlugin({ template: './public/login.html' filename: 'login.html' //打包后的文件名 chunks: ['login'] }) ] }

执行 npm run build,可以看到 index.html 中仅引入了 index JS 文件,而 login.html 中也仅引入了 loginJS 文件,符合我们的预期。

webpack 详细教程:带你深度解读Webpack系列 进阶篇(2)

7.resolve 配置


resolve 配置 webpack 如何寻找模块所对应的文件。webpack 内置 JavaScript 模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你可以根据自己的需要修改默认的规则。

  1. modules

resolve.modules 配置 webpack 去哪些目录下寻找第三方模块,默认情况下,只会去 node_modules 下寻找,如果你我们项目中某个文件夹下的模块经常被导入,不希望写很长的路径,那么就可以通过配置 resolve.modules 来简化。

//webpack.config.js module.exports = { //.... resolve: { modules: ['./src/components' 'node_modules'] //从左到右依次查找 } }

这样配置之后,我们 import Dialog from 'dialog',会去寻找 ./src/components/dialog,不再需要使用相对路径导入。如果在 ./src/components 下找不到的话,就会到 node_modules 下寻找。

  1. alias

resolve.alias 配置项通过别名把原导入路径映射成一个新的导入路径,例如:

//webpack.config.js module.exports = { //.... resolve: { alias: { 'react-native': '@my/react-native-web' //这个包名是我随便写的哈 } } }

例如,我们有一个依赖 @my/react-native-web 可以实现 react-native 转 web。我们代码一般下面这样:

import { View ListView StyleSheet Animated } from 'react-native';

配置了别名之后,再转 web 时,会从 @my/react-native-web 寻找对应的依赖。

当然啦,如果某个依赖的名字太长了,你也可以给它配置一个短一点的别名,这样用起来比较爽,尤其是带有 scope 的包。

  1. extensions

适配多端的项目中,可能会出现 .web.js .wx.js,例如在转web的项目中,我们希望首先找 .web.js,如果没有,再找 .js。我们可以这样配置:

//webpack.config.js module.exports = { //.... resolve: { extensions: ['web.js' '.js'] //当然,你还可以配置 .json .css } }

首先寻找 ../dialog.web.js ,如果不存在的话,再寻找 ../dialog.js。这在适配多端的代码中非常有用,否则,你就需要根据不同的平台去引入文件(以牺牲了速度为代价)。

import dialog from '../dialog';

当然,配置 extensions,我们就可以缺省文件后缀,在导入语句没带文件后缀时,会自动带上extensions 中配置的后缀后,去尝试访问文件是否存在,因此要将高频的后缀放在前面,并且数组不要太长,减少尝试次数。如果没有配置 extensions,默认只会找对对应的js文件。

  1. enforceExtension

如果配置了 resolve.enforceExtension true,那么导入语句不能缺省文件后缀。

  1. mainFields

有一些第三方模块会提供多份代码,例如 bootstrap,可以查看 bootstrap 的 package.json 文件:

{ "style": "dist/css/bootstrap.css" "sass": "scss/bootstrap.scss" "main": "dist/js/bootstrap" } 复制代码

resolve.mainFields 默认配置是 ['browser' 'main'],即首先找对应依赖 package.json 中的 brower 字段,如果没有,找 main 字段。

如:import 'bootstrap' 默认情况下,找的是对应的依赖的 package.json main 字段指定的文件,即 dist/js/bootstrap

假设我们希望,import 'bootsrap' 默认去找 css 文件的话,可以配置 resolve.mainFields 问:

//webpack.config.js module.exports = { //.... resolve: { mainFields: ['style' 'main'] } }

8.区分不同的环境


目前为止我们 webpack 的配置,都定义在了 webpack.config.js 中,对于需要区分是开发环境还是生产环境的情况,我们根据 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 install webpack-merge -D

const merge = require('webpack-merge'); merge({ devtool: 'cheap-module-eval-source-map' module: { rules: [ {a: 1} ] } plugins: [1 2 3] } { devtool: 'none' mode: "production" module: { rules: [ {a: 2} {b: 1} ] } plugins: [4 5 6] }); //合并后的结果为 { devtool: 'none' mode: "production" module: { rules: [ {a: 1} {a: 2} {b: 1} ] } plugins: [1 2 3 4 5 6] }

webpack.config.base.js 中是通用的 webpack 配置,以 webpack.config.dev.js 为例,如下:

//webpack.config.dev.js const merge = require('webpack-merge'); const baseWebpackConfig = require('./webpack.config.base'); module.exports = merge(baseWebpackConfig { mode: 'development' //...其它的一些配置 });

然后修改我们的 packag.json,指定对应的 config 文件:

//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.prod.js" } }

你可以使用 merge 合并,也可以使用 merge.smart 合并,merge.smart 在合并loader时,会将同一匹配规则的进行合并,webpack-merge 的说明文档中给出了详细的示例。

9.定义环境变量


很多时候,我们在开发环境中会使用预发环境或者本地的域名,生产环境中使用线上域名,我们可以在 webpack 定义环境变量,然后在代码中使用。

使用 webpack 内置插件 DefinePlugin 来定义环境变量。

DefinePlugin 中的每个键,是一个标识符.

  • 如果 value 是一个字符串,会被当做 code 片段
  • 如果 value 不是一个字符串,会被 stri.jpgy
  • 如果 value 是一个对象,正常对象定义即可
  • 如果 key 中有 typeof,它只针对 typeof 调用定义

//webpack.config.dev.js const webpack = require('webpack'); module.exports = { plugins: [ new webpack.DefinePlugin({ DEV: JSON.stri.jpgy('dev') //字符串 FLAG: 'true' //FLAG 是个布尔类型 }) ] }

//index.js if(DEV === 'dev') { //开发环境 }else { //生产环境 }

webpack 详细教程:带你深度解读Webpack系列 进阶篇(3)

10.利用webpack解决跨域问题


假设前端在 3000 端口,服务端在 4000 端口,我们通过 webpack 配置的方式去实现跨域。

首先,我们本地创建一个 server.js

let express = require('express'); let app = express(); app.get('/api/user' (req res) => { res.json({name: '刘小夕'}); }); app.listen(4000);

执行代码(run code),现在我们可以在浏览器中访问到此接口: http://localhost:4000/api/user

index.js 中请求 /api/user,修改 index.js 如下:

//需要将 localhost:3000 转发到 localhost:4000(服务端) 端口 fetch("/api/user") .then(response => response.json()) .then(data => console.log(data)) .catch(err => console.log(err));

我们希望通过配置代理的方式,去访问 4000 的接口。

配置代理

修改 webpack 配置:

//webpack.config.js module.exports = { //... devServer: { proxy: { "/api": "http://localhost:4000" } } }

重新执行 npm run dev,可以看到控制台打印出来了 {name: "刘小夕"},实现了跨域。

大多情况,后端提供的接口并不包含 /api,即:/user,/info、/list 等,配置代理时,我们不可能罗列出每一个api。

修改我们的服务端代码,并重新执行。

//server.js let express = require('express'); let app = express(); app.get('/user' (req res) => { res.json({name: '刘小夕'}); }); app.listen(4000);

尽管后端的接口并不包含 /api,我们在请求后端接口时,仍然以 /api 开头,在配置代理时,去掉 /api,修改配置:

//webpack.config.js module.exports = { //... devServer: { proxy: { '/api': { target: 'http://localhost:4000' pathRewrite: { '/api': '' } } } } }

11.前端模拟数据


简单数据模拟

module.exports = { devServer: { before(app) { app.get('/user' (req res) => { res.json({name: '刘小夕'}) }) } } }

src/index.js 中直接请求 /user 接口。

fetch("user") .then(response => response.json()) .then(data => console.log(data)) .catch(err => console.log(err));

使用 mocker-api mock 数据接口

mocker-api 为 REST API 创建模拟 API。在没有实际 REST API 服务器的情况下测试应用程序时,它会很有用。

  1. 安装 mocker-api:

npm install mocker-api -D

  1. 在项目中新建mock文件夹,新建 mocker.js文件,文件如下:

module.exports = { 'GET /user': {name: '刘小夕'} 'POST /login/account': (req res) => { const { password username } = req.body if (password === '888888' && username === 'admin') { return res.send({ status: 'ok' code: 0 token: 'sdfsdfsdfdsf' data: { id: 1 name: '刘小夕' } }) } else { return res.send({ status: 'error' code: 403 }) } } }

  1. 修改 webpack.config.base.js:

const apiMocker = require('mocker-api'); module.export = { //... devServer: { before(app){ apiMocker(app path.resolve('./mock/mocker.js')) } } }

这样,我们就可以直接在代码中像请求后端接口一样对mock数据进行请求。

  1. 重启 npm run dev,可以看到,控制台成功打印出来 {name:'刘小夕'}
  2. 我们在修改下 src/index.js,检查下POST接口是否成功

//src/index.js fetch("/login/account" { method: "POST" headers: { 'Accept': 'application/json' 'Content-Type': 'application/json' } body: JSON.stri.jpgy({ username: "admin" password: "888888" }) }) .then(response => response.json()) .then(data => console.log(data)) .catch(err => console.log(err));

可以在控制台中看到接口返回的成功的数据。

进阶篇就到这里结束啦,最后一篇是优化篇,准备好小板凳和瓜子来约哦~~

webpack 详细教程:带你深度解读Webpack系列 进阶篇(4)

猜您喜欢: