Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

从零开始学webpack:进阶配置 #13

Open
mobei95 opened this issue Apr 26, 2020 · 0 comments
Open

从零开始学webpack:进阶配置 #13

mobei95 opened this issue Apr 26, 2020 · 0 comments
Labels
webpack webpack整理

Comments

@mobei95
Copy link
Owner

mobei95 commented Apr 26, 2020

前一篇文章中介绍了一些webpack配置常用的loader和插件,并且完成了一个适合大多数场景的基础配置文件;本文将继续介绍webpack的配置,相对于上一篇文章,本文更加着重开发效率和个性化需求的配置

多页应用打包

前一篇文章中配置都只适用于单页应用的打包,但是在实际的工作也还会涉及到多页应用的项目开发;

webpack中多页应用与单页应用打包的不同之处主要体现在以下几点:

  • 多入口,不同的页面使用不同的入口文件
  • 多出口,入口和出口相对应,有多个入口也就有了多个出口
  • 多HTML,不同的入口可以输出不同的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值,在单页应用中默认为main
chunks: 用于指定需要引入到html中的js文件;与它相反,还有一个excludeChunks参数,它用于指定不需要引入的js文件

静态数据拷贝

日常的开发中会遇到一些直接使用的文件,这些文件可能是js,也可能是css或者是图片,它们不需要经过打包,可以直接将他们拷贝到webpack构建目录;但是手动拷贝不仅麻烦并且容易出错(比如,修改之后忘记拷贝)

copy-webpack-plugin插件用于将指定文件/文件夹复制到构建目录;通过这个插件可以将静态文件直接复制到输出目录中

安装

npm install copy-webpack-plugin -D

配置

<!-- 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配置,可以参考官方文档

devTool(配置sourceMap)

在开发的过程中经常会通过控制台查看错误信息;但是打包之后的错误信息位置将会以构建后的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解决跨域问题

跨域是前后端接口交互时一个很常见的问题,解决跨域发方式也有很多,这里主要介绍如何通过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需要使用到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来实现跨域

mock数据

webpack-dev-server提供了一个before钩子;它的第一个参数暴露了webpack-dev-server内部的express服务,通过这个服务,可以完成一些数据的mock

普通数据mock

<!-- webpack.config.js -->
module.exports = {
    devServer: {
        before(app) {
            app.get('/name', (req, res) => {
                res.json('阿白Smile')
            })
        }
    }
}   

使用mockerAPI

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'
        });
    }
}

配置到devServerbefore钩子

<!-- 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"}

配置resolve属性

webpack启动后会从入口文件开始找出所有依赖的模块;resolve的作用就是告诉webpack如何寻找这些模块所对应的文件

alias (配置别名)

resolve.alias用于配置别名,通过别名可以把原来的导入路径映射到一个新的路径, 它可以让模块的引入更加简单;vue中常常为src文件夹设置一个别名为@

文件结构

|--src
|--|--assets
|--|--|--main.css

配置

<!-- webpack.config.js -->
module.exports = {
    // 省略其他代码 
    resolve: {
        alias: {
            '@': './src'
        }
    }
}

使用

import '@/assets/main.css'

使用别名之后,别名会直接映射到别名所指定的路径;在使用相关模块时,可以使用更加简单的方式书写模块路径

modules

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中查找;

extensions (扩展名配置)

在模块导入语句中没有带文件后缀时,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

@mobei95 mobei95 added the webpack webpack整理 label Apr 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
webpack webpack整理
Projects
None yet
Development

No branches or pull requests

1 participant