Webpack
1. watch观察模式
在每一次修改代码后,我们都要执行npm run build
进行打包,这样不是很方便。为了简化流程,我们有两种方式。
第一,可以在package.json中添加watch
,之后每一次修改代码保存后都会自动打包一次。
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --watch"
},
用VScode的Live Server
打开的页面也会自动更新
第二,在webpack.config.js中配置watch
module.exports={
watch:true,
...
}
2. webpack-dev-server
使用watch
和live server
可以实现自动更新,但是其存在问题为:
- 修改时,所有源代码都会重新编译
- 每次编译成功后都要进行文件读写(dist文件生成)
- 不能实现局部更新
这时我们需要webpack-dev-server
, webpack-dev-server
为你提供了一个简单的 web 服务器,并且能够实时重新加载。
1、下载
npm i webpack-dev-server -D
2、在package.json
中配置serve
如下
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"serve": "webpack serve"
},
3、在终端执行npm run serve
,这时并不会生成dist文件,而是将资源保存在内存中。我们可以使用http://localhost:8080/
打开页面,从而使我们的资源运行在一个本地服务器上。
除此之外,我们还可以在webpack.config.js中配置devServer。
devServer:{
static:{
// 此路径下的打包文件可在浏览器中访问。
//默认 publicPath 是 "/",所以你的包index.js可以通过 http://localhost:8080/index.js 访问。
publicPath:'/lg/',
}
},
这时如果把output.publicPath
也设置为'/lg'
则可以在该目录下正确访问页面
其他配置:
devServer:{
// 在构建失败时不刷新页面作为回退 ,也就是说当页面更新有语法错误时,不会刷新页面
hot:'only',
// 指定服务开启的端口号
port:7788,
// 在服务器已经启动后自动打开浏览器
open:true
// 对文件进行压缩处理后再返回给客户端进行展示,有性能提升
compress: true,
// 刷新页面后如果没有找到对应的资源,就会显示index.html
historyApiFallback: true,
// 配置代理服务器,解决跨域资源请求问题
proxy: {
'/api': {
// 对 /api/users 的请求会将请求代理到 http://localhost:3000/api/users。
target: 'http://localhost:3000',
// 如果不希望传递/api,则需要重写路径
pathRewrite: { '^/api': '' },
// ture表示不接受在 HTTPS 上运行且证书无效的后端服务器
secure: false,
// 默认情况下,代理时会保留主机头的来源,可以将 changeOrigin 设置为 true 以覆盖此行为。
changeOrigin: true,
},
},
},
3. webpack-dev-middleware
webpack-dev-middleware
是一个容器,它可以把 webpack 处理后的文件传递给一个服务器。
// express框架用于搭建web服务器
npm i express
npm i webpack-dev-middleware
express框架的基本使用可以看这一篇文章
const webpackDevMiddleware=require('webpack-dev-middleware')
const webpack = require('webpack')
const express = require('express')
const app = express()
// 获取配置文件
const config = require('./webpack.config.js')
const compiler = webpack(config)
app.use(webpackDevMiddleware(compiler))
//开启端口上的服务
app.listen(3000,()=>{
console.log('服务器运行在3000端口');
})
这样我们就可以访问express所搭建的服务器资源了
4. HMR
模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新。
也就是说如果当你更新一个模块的代码时,其他未修改的代码不会受影响,重新更新。
1、webpack.config.js
中的配置如下
module.exports={
...
devServer:{
hot:true
}
}
2、在入口文件index.js中指定要热替换的模块
if(module.hot){
module.hot.accept(['./title.js'],()=>{
console.log('title模块更新啦~');
})
}
3、局部更新
5. Vue组件支持热更新
Vue Loader支持用于 vue 组件的 HMR,提供开箱即用体验。
1、下载
npm i vue@2 -D
npm i vue-loader@14 -D
2、webpack.config.js中进行配置
{
test:/\.vue$/,
use:['vue-loader']
}
3、示例
index.js
import './title'
import Vue from 'vue'
import App from './App.vue'
new Vue({
render:h=>h(App)
}).$mount('#root')
app.vue
<template>
<div class='one'>{{msg}}</div>
</template>
<script>
export default{
data(){
return{
msg:'hello!'
}
}
}
</script>
<style>
.one{
color:red;
}
</style>
如下:
6. 解析Resolve
Resolve
配置可以设置模块如何被解析
绝对路径:不需要进行解析
相对路径:在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径。
模块路径:在 resolve.modules 中指定的所有目录中检索模块。
modules: ['node_modules']
一旦根据上述规则解析路径后,resolver 将会检查路径是指向文件还是文件夹。
如果路径指向文件:
- 如果文件具有扩展名,则直接将文件打包。
- 否则,将使用
resolve.extensions
选项作为文件扩展名来解析,此选项会告诉解析器在解析中能够接受那些扩展名(例如 .js,.jsx)。
extensions: ['.js', '.json', '.wasm'],
如果路径指向一个文件夹,则进行如下步骤寻找具有正确扩展名的文件:
- 如果文件夹中包含 package.json 文件,则会根据
resolve.mainFields
配置中的字段顺序查找,并根据 package.json 中的符合配置要求的第一个字段来确定文件路径。
mainFields: ['browser', 'module', 'main'],
- 如果不存在 package.json 文件或 resolve.mainFields 没有返回有效路径,则会根据
resolve.mainFiles
配置选项中指定的文件名顺序查找,看是否能在 import/require 的目录下匹配到一个存在的文件名。
mainFiles: ['index'],
- 然后使用
resolve.extensions
选项,以类似的方式解析文件扩展名。
resolve.alias
用于创建 import 或 require 的别名,来确保模块引入变得更简单。例如,一些位于 src/ 文件夹下的常用模块:
resolve: {
alias: {
@: path.resolve(__dirname, 'src')
},
},
原本导入如下
import './title'
现在导入
import '@/title'
7. source-map
当 webpack 打包源代码时,可能会很难追踪到 error(错误) 和 warning(警告) 在源代码中的原始位置。例如,如果将三个源文件(a.js, b.js 和 c.js)打包到一个 bundle(bundle.js)中,而其中一个源文件包含一个错误,那么堆栈跟踪就会直接指向到 bundle.js。
为了更容易地追踪 error 和 warning,JavaScript 提供了 source maps 功能,可以将编译后的代码映射回原始源代码。如果一个错误来自于 b.js,source map 就会明确的告诉你。
devtool:'source-map'
浏览器中要sour允许使用source-map
8. devtool
devtool用于选择一种 source map 风格来增强调试过程。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。
- 对于开发环境,通常希望更快速的 source map,需要添加到 bundle 中以增加体积为代价
- 对于生产环境,则希望更精准的 source map,需要从 bundle 中分离并独立存在。
以下选项非常适合开发环境:
eval
- 每个模块都使用 eval() 执行,并且都有 //@ sourceURL。此选项会非常快地构建。主要缺点是,由于会映射到转换后的代码,而不是映射到原始代码(没有从 loader 中获取 source map),所以不能正确的显示行数。eval-source-map
- 每个模块使用 eval() 执行,并且 source map 转换为 DataUrl 后添加到 eval() 中。初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。eval-cheap-source-map
- 类似 eval-source-map,每个模块使用 eval() 执行。这是 “cheap(低开销)” 的 source map,因为它没有生成列映射(column mapping),只是映射行数。它会忽略源自 loader 的 source map,并且仅显示转译后的代码,就像 eval devtool。eval-cheap-module-source-map
- 类似 eval-cheap-source-map,并且,在这种情况下,源自 loader 的 source map 会得到更好的处理结果。然而,loader source map 会被简化为每行一个映射(mapping)。
这些选项通常用于生产环境中:
(none)
(省略 devtool 选项) - 不生成 source map。这是一个不错的选择。source-map
- 整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它。
以下选项对于开发环境和生产环境并不理想。他们是一些特定场景下需要的,例如,针对一些第三方工具。
inline-source-map
- source map 转换为 DataUrl 后添加到 bundle 中。cheap-source-map
- 没有列映射(column mapping)的 source map,忽略 loader source map。inline-cheap-source-map
- 类似 cheap-source-map,但是 source map 转换为 DataUrl 后添加到 bundle 中。cheap-module-source-map
- 没有列映射(column mapping)的 source map,将 loader source map 简化为每行一个映射(mapping)。inline-cheap-module-source-map
- 类似 cheap-module-source-map,但是 source mapp 转换为 DataUrl 添加到 bundle 中。
9. 区分环境配置
在开发环境中,我们需要强大的 source map 和一个有着 live reloading(实时重新加载) 或 hot module replacement(热模块替换) 能力的 localhost server。
而生产环境目标则转移至其他方面,关注点在于压缩 bundle、更轻量的 source map、资源优化等,通过这些优化方式改善加载时间。
由于要遵循逻辑分离,建议为每个环境编写彼此独立的 webpack 配置。
当 webpack 配置对象导出为一个函数时,可以向起传入一个"环境对象(environment)"。
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"serve": "webpack serve",
"build2": "webpack --config ./config/webpack.common.js --env production",
"serve2": "webpack serve --config ./config/webpack.common.js --env development"
},
根据CLI命令绝对配置文件
// webpack-merge 提供一个merge函数用于合并数组,或者对象
const {merge} = require('webpack-merge')
const prodConfig = require('./webpack.prod')
const devConfig = require('./webpack.dev')
const commonConfig={
//基本配置
}
module.exports=(env)=>{
const isProduction = env.Production;
const config = isProduction?prodConfig:devConfig;
const mergeConfig=merge(commonConfig,config)
return mergeConfig
}