在前一篇文章中介绍了一些webpack配置经常使用的loader和插件,而且完成了一个适合大多数场景的基础配置文件;本文将继续介绍webpack的配置,相对于上一篇文章,本文更加着重开发效率和个性化需求的配置javascript
前一篇文章中配置都只适用于单页应用的打包,可是在实际的工做也还会涉及到多页应用的项目开发;css
webpack中多页应用与单页应用打包的不一样之处主要体如今如下几点:html
配置前端
<!-- webpack.config.js -->
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
main: './main.js',
miniApp: './miniApp.js'
},
output: {
filename: [name].[hsah].js,
path: path.resolve(__dirname, 'dist')
},
plugin: [
new HtmlWebpackPlugin({
template: './index.html',
filename: 'main.html',
chunks: ['main']
}),
new HtmlWebpackPlugin({
template: './index.html',
filename: 'miniApp.html',
chunks: ['miniApp']
})
]
// 省略其余代码
}
复制代码
[name]
: 构建包的名称,它来源于entry中的key
值,在单页应用中默认为mainvue
chunks
: 用于指定须要引入到html中的js文件;与它相反,还有一个excludeChunks
参数,它用于指定不须要引入的js文件java
平常的开发中会遇到一些直接使用的文件,这些文件多是js,也多是css或者是图片,它们不须要通过打包,能够直接将他们拷贝到webpack构建目录;可是手动拷贝不只麻烦而且容易出错(好比,修改以后忘记拷贝)node
copy-webpack-plugin
插件用于将指定文件/文件夹复制到构建目录;经过这个插件能够将静态文件直接复制到输出目录中webpack
安装git
npm install copy-webpack-plugin -D
复制代码
配置github
<!-- webpack.config.js -->
const path = require('path')
const copyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
plugin: [
new copyWebpackPlugin([
{
from: path.resolve(__dirname, './public'),
to: './dist'
}
])
]
}
复制代码
from
: 指定须要复制的文件夹
to
: 指定复制后的文件夹
更多copyWebpackPlugin
配置,能够参考官方文档
在开发的过程当中常常会经过控制台查看错误信息;可是打包以后的错误信息位置将会以构建后的js为基准,这样的错误信息由于没有定位到源码的错误位置,对调试很不友好
这个问题能够经过对devTool
配置sourceMap
来解决;sourceMap是一个源码映射文件,有多种格式可选,这里只列举几个有表明性的,更多sourceMap格式能够查看官方文档
source-map
: 原始源代码,会单独生成源码文件,能够提示错误信息的列和行eval-source-map
: 原始源代码,不会产生单独的文件,可是能够显示行和列cheap-module-source-map
: 转换后的代码,生成单独的文件,能够提示行但不能提示列cheap-module-eval-source-map
: 原始源代码,集成在打包后的文件中,不会生成独立的文件,能够提示行,但不能提示列综合实际使用状况和构建速度考虑,开发环境中通常使用cheap-module-eval-source-map
<!-- webpack.config.js -->
module.exports = {
// 省略其余代码
devtool: 'cheap-module-eval-source-map'
}
复制代码
跨域是先后端接口交互时一个很常见的问题,解决跨域发方式也有不少,这里主要介绍如何经过webpack的配置来解决跨域问题;
先经过server.js
在本地建立一个node服务, 启动本地的3000端口做为后端服务
<!-- server.js -->
const express = require('express')
const app = express()
app.get('/api/user', (req, res) => {
res.json({name: '阿白Smile'})
})
app.listen(3000)
复制代码
使用命令行工具执行node server.js
命令启动node服务,而后使用client.js
向后端服务发送请求
<!-- client.js -->
const xhr = new XMLHtttpRequest()
xhr.open('GET', '/api/user', true)
xhr.onload = function() {
console.log(xhr.response)
}
xhr.send()
复制代码
接下来就经过webpack为前端服务配置一个代理,将前端的服务代理到后端的服务上,这样可让前端和服务端在同一个服务下,以此来解决跨域问题
<!-- webpack.config.js -->
module.exports = {
devServer: {
prot: 3000,
host: 'localhost'
proxy: {
'/api': 'http://localhost:3000'
}
}
}
复制代码
经过以上的配置,当client.js
访问/api/user
时,请求会被代理到http://localhost:3000/api/user
若是不想每次都在接口路径前加/api
,或者后端的接口没有/api
这一层路径,那么能够经过pathRewrite
重写路径,将/api
重写为空
<!-- webpack.config.js -->
module.exports = {
devServer: {
prot: 3000,
host: 'localhost'
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {'/api': ''}
}
}
}
}
复制代码
经过服务端启动webpack,能够将前端和服务端启动在同一个服务上;当先后端都在同一个服务的时候天然就不存在跨域问题了
服务端启动webpack须要使用到webpack-dev-middleware
中间件
安装
npm install webpack-dev-middleware -D
复制代码
配置
<!-- server.js -->
const express = require('express')
const webpack = require('webpack')
const middle = require('webpack-dev-middleware')
const app = express()
let config = require('./webpack.config.js')
let compiler = webpack(config)
app.use(middle(compiler))
app.get('/user', (req, res) => {
res.json({name: '阿白Smile'})
})
app.listen(3000)
复制代码
使用这种方式的前提是可以操做服务器,不过既然均可以操做服务器了,那么还能够经过设置header
来实现跨域
webpack-dev-server
提供了一个before钩子
;它的第一个参数暴露了webpack-dev-server
内部的express
服务,经过这个服务,能够完成一些数据的mock
<!-- webpack.config.js -->
module.exports = {
devServer: {
before(app) {
app.get('/name', (req, res) => {
res.json('阿白Smile')
})
}
}
}
复制代码
mocker-api
是一个为 REST API 建立mock的webpack-dev-server
中间件。在后端服务尚未完成的时候,能够经过这个中间件进行mock数据
安装
npm install mocker-api -D
复制代码
配置
建立一个mock.js
进行接口和数据mock
<!-- mock.js -->
module.exports = {
'GET /userInfo/:id': (req, res) => {
const { id } = req.params;
// 省略查询
return res.json({
id,
name: '阿白smile'
});
}
}
复制代码
配置到devServer
的before钩子
中
<!-- webpack.config.js -->
const path = require('path')
const apiMocker = require('mocker-api');
const mockApi = path.resolve('./mock.js')
module.exports = {
devServer: {
before(app) {
mockApi(app, mockApi)
}
}
}
复制代码
修改client.js
文件,访问/userInfo
接口
<!-- client.js -->
const xhr = new XMLHtttpRequest()
xhr.open('GET', '/userInfo/1', true)
xhr.onload = function() {
console.log(xhr.response)
}
xhr.send()
复制代码
使用npm run dev
命令执行脚本以后,在浏览器中打开,此时浏览器会打印出{"id":"1","name":"阿白smile"}
webpack启动后会从入口文件开始找出全部依赖的模块;resolve
的做用就是告诉webpack如何寻找这些模块所对应的文件
resolve.alias
用于配置别名,经过别名能够把原来的导入路径映射到一个新的路径, 它可让模块的引入更加简单;vue中经常为src
文件夹设置一个别名为@
文件结构
|--src
|--|--assets
|--|--|--main.css
复制代码
配置
<!-- webpack.config.js -->
module.exports = {
// 省略其余代码
resolve: {
alias: {
'@': './src'
}
}
}
复制代码
使用
import '@/assets/main.css'
复制代码
使用别名以后,别名会直接映射到别名所指定的路径;在使用相关模块时,可使用更加简单的方式书写模块路径
resolve.modules
用于指定webpack解析模块时从哪些目录下搜索模块,默认状况下,webpack只从node_modules
中搜索;
若是有一个模块是本身编写的文件,那么它就不须要到node_modules
中取查找;好比, 咱们常常会在src/components
中编写一些组件,为了不每次引用组件都写很长的路径,就能够经过modules
配置来简化如下路径
文件结构
|--src
|--|--components
|--|--|--navMenu.vue
复制代码
配置
<!-- webpack.config.js -->
module.exports = {
// 省略其余代码
resolve: {
modules: ['./src/components', 'node_modules']
}
}
复制代码
使用
import navMenu from 'navMenu'
复制代码
webpack在搜索模块时,会根据modules
的配置从左到右开始搜索;因此,在使用navMenu
时,会先从./src/components
中查找,若是没有找到,则会去node_modules
中查找;
在模块导入语句中没有带文件后缀时,webpack会自动带上尝试后缀去访问(默认带.js
); resolve.extensions
用于配置尝试访问的后缀列表,列表的优先级为从左到右
配置
<!-- webpack.config.js -->
module.exports = {
// 省略其余代码
resolve: {
extensions: ['.js', '.vue', '.json']
}
}
复制代码
使用
import index from './index'
复制代码
上面的代码结合配置以后,会先尝试使用.js
做为后缀去访问index,若是不存在则尝试使用.vue
做为后缀去访问;以此类推,若是配置的后缀都尝试了尚未找到文件,则会报错,文件未找到
在实际开发中,开发环境和线上生产环境每每会使用不一样的服务和域名,而且不一样的环境应该是能够自动切换的,这就须要在代码中使用环境变量来区分不一样的环境,而后自动切换相应的服务和域名
webpack经过内置的DefinePlugin
插件能够提供了一个区分环境的全局变量
DefinePlugin
的键值都是一个标志符或者多个用 .
链接起来的标志符
若是value
是一个字符串,它会被看成一个代码片断来使用
若是value
不是字符串,它会被转化为字符串(包括函数)
若是value
是一个对象,它全部的 key
会被一样的方式定义(即全局能够直接访问对象的key,value
则会应用DefinePlugin
的键值规则)
若是在一个 key
前面加了 typeof
,它会被定义为 typeof
调用
module.exports = { plugin: { new webpack.DefinePlugin({ DEV: JSON.stringify('dev') }) } }
if (DEV) { // 开发 BASE_URL: '' // 开发服务 } else { // 生产 BASE_URL: '' // 生产服务 }
经过环境变量能够进行环境的区分,可是代码中若是涉及到多处须要区分且每次都须要手动去配置,这不只加大了工做量并且还容易出错;
通常的解决方案是将不一样环境的配置分开到不一样的文件,而后使用webpack-merge
将其与基础配置进行合并
webpack.base.config.js
: 基础配置文件 webpack.pro.config.js
: 线上生产环境配置文件 webpack.dev.config.js
: 开发环境配置文件
webpack-merge
是一个函数,它提供了合并功能,接收一个或多个对象/数组,用于对象的合并与数组的链接,返回合并后的对象;在合并对象时,若是同一个key出现屡次,则后面的覆盖前面的
安装
npm install webpack-merge
复制代码
配置
const merge = require('webpack-merge')
let result = merge({name: '阿白smile', age: 18}, {age: 24, location: '北京'})
// 合并后的结果
// {name: '阿白smile', age: 24, location: '北京'}
复制代码
webpack.base.config.js
是基础的webpack配置,各个环境能够通用,因此webpack.base.config.js
须要做为merge的第一个参数
开发环境的配置
<!-- webpack.dev.config.js -->
const baseWebpackConfig = require('./webpack.base.config')
const merge = require('webpack-merge')
let devWebpackConfig = merge(baseWebpackConfig, {
mode: 'development',
devServer: {
// 省略其余代码
}
})
moudle.export = devWebpackConfig
复制代码
生产环境的配置
<!-- webpack.pro.config.js -->
const baseWebpackConfig = require('./webpack.base.config')
const merge = require('webpack-merge')
let proWebpackConfig = merge(baseWebpackConfig, {
mode: 'production',
// 省略其余代码
})
moudle.export = proWebpackConfig
复制代码
配置命令脚本
<!-- package.json -->
"scripts": {
"dev": "webpack-dev-server --config=webpack.dev.config.js"
"build": "webpack --config=webpack.pro.config.js"
}
复制代码
热更新主要应用在开发环境,当代码更改以后页面上只更新被修改的部分,不须要刷新页面,对开发和调试很是有利;
webpack中的热更新的配置主要依赖devServer.hot
以及webpack的内置插件HotModuleReplacementPlugin
<!-- webpack.config.js -->
module.exports = {
devServer: {
hot: true // 启用热更新
},
plugin: [
new webpack.HotModuleReplacementPlugin() // 热更新插件
]
}
复制代码
以上的代码配置以后在浏览器查看,会发现仍是会刷新整个页面;这个时候还须要在入口文件中作如下配置
if (module.hot) {
module.hot.accept()
}
复制代码
module.hot
用于通知webpack此模块能够用于热更新,更多信息能够参考官方文档
经过本篇文章中介绍的webpack配置,能够知足更加个性化的开发需求以及更高效率的开发
本文所对应的配置源码已提交到个人github
END