Webpack5 从零配置一个基础的 Vue 项目 您所在的位置:网站首页 陀螺仪超频助手app安卓版 Webpack5 从零配置一个基础的 Vue 项目

Webpack5 从零配置一个基础的 Vue 项目

2023-03-27 22:26| 来源: 网络整理| 查看: 265

前言

配置日期2021/6/28,使用的 Node.js 版本14.15.4,最低版本建议升至10.16.0+ 。本文记录使用 Webpack5 手动从零配置一个基础 Vue3 项目的学习过程。

用于参考的项目文件地址:gitee.com/willflow/ju…

若希望使用 vue-cli 创建 Vue3 项目:

安装或者升级 vue-cli:npm install -g @vue/cli 保证 vue-cli 版本在4.5.0以上:vue --version 创建项目选择 vue3.0:vue create 项目名 windows 用户命令行无法移动光标可以使用这个命令创建项目:winpty vue.cmd create 项目名

文中各配置项,皆为基础使用方法,更多配置请参考官方文档。文中如有不当之处,还望不吝指正,理性交流。

第一部分:初始化项目

目标:完成一个能够打包运行的最基础配置,为后续逐步完善做准备。 示例的基础目录结构如下

image.png

创建项目目录、初始化 package.json、安装Webpack 并添加配置文件 webpack.config.js # 创建文件夹,并且进入 mkdir demo && cd $_ # 初始化package.json npm init // 或者全部使用默认选项 npm init -y # 安装 webpack 和 webpack CLI到开发依赖 npm i webpack -D npm i webpack-cli -D //webpack命令执行之后,会寻找webpack-cli这个包并执行,详见入口文件的逻辑:node_modules/webpack/bin/webpack.js 复制代码

根目录创建 webpack.config.js 文件并配置 entry 和 output

//webpack.config.js const path = require('path'); module.exports = { entry: './src/main.js', // 配置入口文件,单入口使用字符串,多入口使用对象 output: { // 打包后项目文件在硬盘中的存储位置 filename: '[name].js', // [name]占位符的写法可支持多入口 path: path.resolve(__dirname, 'dist'), }, mode: 'production', // 告知 webpack 使用相应模式的内置优化,默认为 production } 复制代码 Vue 和相关依赖 npm i vue -S //-S 等于 --save -D 等于 --save-dev i 等于 install npm i vue-loader -D //解析和转换.vue文件。提取出其中的逻辑代码 script,样式代码style,以及HTML 模板template,再分别把他们交给对应的loader去处理 npm i @vue/compiler-sfc -D //将解析完的vue单页面组件(sfc)编译为js 复制代码 //webpack.config.js const { VueLoaderPlugin } = require('vue-loader'); module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', }, ]; } plugins: [ new VueLoaderPlugin(), ] 复制代码

在main.js 中使用 Vue

import { createApp } from 'vue' import App from './App.vue' // 测试文件 App.vue 和 用于挂载的index.html 自行创建 createApp(App).mount('#app') 复制代码 CSS 相关依赖 npm i css-loader -D // 用于处理 .css 文件 npm i style-loader -D //将样式通过 标签的形式 挂载到页面的 head 部分 复制代码 //webpack.config.js module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' // loader执行从右到左,这里先执行css-loader ] }, ]; } 复制代码 html-webpack-plugin npm i html-webpack-plugin -D //在打包结束后,自动生成一个html文件,并把打包生成的js文件引入到这个html文件当中 复制代码 //webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin'); plugins: [ new HtmlWebpackPlugin({ template: path.join(__dirname, 'index.html'), filename: 'index.html' }), ] 复制代码 clean-webpack-plugin npm i clean-webpack-plugin -D //在打包之前清空output配置的文件夹 复制代码 // //webpack.config.js const { CleanWebpackPlugin } = require('clean-webpack-plugin'); plugins: [ new CleanWebpackPlugin() ] 复制代码 webpack-dev-server npm i webpack-dev-server -D //以file形式在浏览器打开打包后的文件,无法发送ajax请求。所以需要devServer在本地开启一个服务器,以http的形式发送请求。 复制代码 // //webpack.config.js module.exports = { devServer: { contentBase: path.resolve(__dirname, 'dist'), open:true, port:8888, // 第三部分会使用 portfinder 自动获取可用端口号 hot:true, hotOnly:true, }, } 复制代码

webpack-cli4版本,将webpack-dev-server的命令集成到了webpack-cli当中,需使用如下命令:

//package.json "scripts": { "dev": "webpack serve --config webpack.config.js" }, 复制代码

此时,在命令行输入 npm run dev,项目已经可以正常打包启动

第二部分:引入常用库

目标:在项目中引入vuex、vue-router、Element Plus、Axios、ESLint

在src文件夹下新建 router,store,views,components,assets文件夹,目录结构如下 image.png

vuex和vue-router npm i vuex --save npm i vue-router --save 复制代码

配置router/index.js,可选 hash 模式(createWebHashHistory)和 history 模式(createWebHistory),history 模式需要后端支持。

import { createRouter, createWebHashHistory } from 'vue-router' const routes = [ { path: '/', name: 'Home', component: () => import('../views/home.vue') }, { path: '/about', name: 'About', component: () => import(/* webpackChunkName: "about" */ '../views/about.vue') } ] const router = createRouter({ history: createWebHashHistory(), routes }) export default router 复制代码

配置store/index.js

import { createStore } from 'vuex' export default createStore({ state: { }, mutations: { }, actions: { }, modules: { } }) 复制代码

在main.js中引入vue-router和vuex

import { createApp } from 'vue' import App from './App.vue' import store from './store' import router from './router' let app = createApp(App) app.use(store) app.use(router) app.mount('#app') 复制代码

配置和引入完成后,vuex和vue-router就可以正常使用了,示例中的测试页面(home.vue,about.vue)自行创建。

Element Plus npm i element-plus --save 复制代码

采用官网的按需引入方法,减小打包体积,需要下载插件babel-plugin-import

npm i babel-plugin-import -D //将import依赖整个组件库的方法,转换成require依赖具体模块,实现组件的按需加载 复制代码

使用该插件需要babel配置文件:babel.config.js

plugins: [ [ "import", { libraryName: 'element-plus', customStyleName: (name) => { return `element-plus/lib/theme-chalk/${name}.css`; }, }, ], ], // 解释: // 当Babel解析遇到: import { xxx } from 'element-plus' // 会转换成: var xxx = require('element-plus/lib/xxx') // 将本来引入整个名为element-plus的库,转换成引入具体模块'element-plus/lib/xxx'。这样webpack会认为依赖的是具体的xxx而不是整个element-plus。同时引入对应的样式文件。 复制代码

main.js

// 在main.js中引入并使用element-plus import { ElButton, ElSelect } from 'element-plus'; const Elcomponents = [ ElButton, ElSelect ] Elcomponents.forEach(component => { app.use(component ) }) 复制代码 Axios npm i axios -S 复制代码

可以对axios进一步封装

// http.js import axios from 'axios' axios.defaults.baseURL = baseURL // 请求拦截 axios.interceptors.request.use(config => { // do something return config },error => { return Promise.reject(error) }) // 返回拦截 axios.interceptors.response.use(response => { // do something return response },error => { return Promise.reject(error) }) export function get(url, params) { return axios.get(url, { params }).then((res) => { // do something return res.data }).catch((e) => { console.log(e) }) } 复制代码

在vue组件中直接使用,或提取成函数后在组件中使用

// 定义获取数据的函数 import { get } from './http' export function getXxx() { return get('/api/getXxx').then((result) => { // do something return result }) } //使用 import { getXxx } from '...' const list = await getXxx() 复制代码 ESLint npm i eslint -D npm i eslint-plugin-vue -D 复制代码

生成eslint文件

./node_modules/.bin/eslint --init 复制代码

在生成的配置文件中,添加插件

"extends": [ "plugin:vue/vue3-essential", // 校验 Vue3 逻辑代码 "plugin:vue/vue3-strongly-recommended", // 校验 Vue3 模板 "eslint:recommended" // 引入eslint核心功能 ], 复制代码

常见插件: eslint-plugin-node:node的eslint配置 eslint-plugin-promise:promise语法配置 eslint-plugin-import:针对import语法优化 eslint-plugin-standard:基础配置(已废弃)

第三部分:拆分webpack.config.js

目标:从第三部分开始,为了方便开发调试和上线,需要将webpack.config.js拆分为以下三个文件。

webpack.dev.js:开发环境配置 webpack.prod.js:生产环境配置 webpack.common.js:开发和生产环境配置的共享配置

开发环境和生产环境的区别

生产环境可能需要分离CSS成单独文件,以便多个页面共享同一个CSS文件 生产环境需要压缩 HTML/CSS/JS 代码 开发环境需要 SourceMap 文件方便调试 开发环境需要HMR、devServer等功能 ...... 总的来说:开发环境帮助我们快速开发测试联调,生产环境保证上线环境的打包流程是最优化体验。 安装 webpack-merge npm i webpack-merge -D // 用于连接数组,合并对象,如下所示: 复制代码 merge( {a : [1],b:5,c:20}, {a : [2],b:10, d: 421} ) //合并后结果 {a : [1,2] ,b :10 , c : 20, d : 421} 复制代码

引入webpack-merge,按照说明的描述拆分文件,配置很简单,可参考项目文件。

mode

mode 选项用于告知 webpack 使用相应模式的内置优化。可选 development, production 或 none 。通常开发环境使用 development,生产环境使用 production。

module.exports = { mode: 'development', }; 复制代码 环境变量

使用 cross-env,跨操作系统设置 node 环境变量。

npm i cross-env -D 复制代码 "scripts": { "dev": "cross-env NODE_ENV=development webpack serve --config webpack.dev.js", "build": "cross-env NODE_ENV=production webpack --config webpack.prod.js" }, 复制代码 sourcemap

sourcemap 产生的文件映射了压缩后的代码所对应的转换前的源代码位置,解决了代码压缩后难以调试的问题。 具体可参照文档,根据需要配置参数值,不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。

// webpack.dev.js devtool: 'eval-cheap-module-source-map', // webpack.prod.js devtool: 'cheap-module-source-map', 复制代码 第四部分:webpack基础共享配置

目标:完善 webpack.common.js

使用 Babel npm i babel-loader -D // 相当于连接webpack和babel的桥梁,并不会转换js代码 复制代码

在 webpack.base.js 的 rules 中新增一条规则。排除 node_modules 文件夹,因为 NPM 引入的工具库已经经过编译,不需要再重复编译,影响打包速度。

// webpack.base.js { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', } 复制代码

接下来关于 babel polyfill 的配置,可以参考我的前一篇内容,Babel polyfill 常见配置对比

CSS样式相关配置

在第一部分,我们已经安装配置过了css-loader 和 style-loader,使得Webpack可以识别css语法,并将css代码以style标签的形式插入到HTML文件当中,现在我们来进一步完善这一部分的配置。

1. css-loader 的 importLoaders

官方解释:用于配置 css-loader 作用于 @import 的资源之前有多少个 loader。 举个例子: 首先假设我们有两个文件:style.css和body.css。style.css中通过@import 'body.css';引入body.css:

/* style.css */ @import 'body.css'; .style-div { display: flex; } /* body.css */ .body-import { /* body import */ display: flex; } 复制代码

测试用的 webpack 配置项如下

rules: [ { test: /\.css$/, use: [ { loader: 'css-loader', options: { importLoaders: 1 // 另一份配置不使用importLoaders } }, 'postcss-loader' ] } ] 复制代码

之后我们来对比一下,开启了importLoaders=1 与未开启 importLoaders 的打包结果

/* 未开启importLoaders */ .body-import { /* body import */ display: flex; } .style-div { display: -ms-flexbox; display: flex; } -------------------------------------------------------- /* 开启importLoaders=1 */ .body-import { /* body import */ display: -ms-flexbox; display: flex; } .style-div { display: -ms-flexbox; display: flex; } 复制代码

通过对比可以发现

未使用 importLoaders 的 body.css 内的display: flex; 没有自动添加前缀,说明 Postcss 没有作用到@import引入的文件中; 使用了importLoaders=1 的 body.css 内的 display: flex; 添加了前缀,说明 Postcss 作用到了被 @import引入的文件中。 所以基于项目需要,我们需要使用 importLoaders,确保 css-loader 之后的 loader 会正常作用。 2. CSS预处理器

预处理器可以提升开发效率和体验。在我的项目里使用 Sass 作为预处理器。

node-sass 已经不再维护,改用官方推荐的 dart-sass,直接安装 sass-loader 和依赖的 sass 即可。

npm i sass sass-loader -D // sass-loader只是将 Sass语法编译成 CSS,后续还需要使用 css-loader 和 style-loader配合使用 复制代码 module.exports = { // ... module: { rules: [ { test: /\.sass$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, 'sass-loader' ] } ] } }; 复制代码 3. PostCSS 和 autoprefixer

PostCSS 能将 CSS 解析成 AST,然后通过各种插件做各种转换,最终生成处理后的新 CSS,跟 Babel 在功能和实现上都类似。我们这里安装 PostCSS,并使用 autoprefixer 实现自动添加浏览器前缀的功能。

npm i postcss-loader postcss autoprefixer -D 复制代码 module: { rules: [ { test: /\.sass$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2 } }, 'sass-loader', 'postcss-loader' ] } ] } 复制代码

在根目录创建 postcss.config.js 和 .browserslistrc 文件来使用 autoprefixer 。

// postcss.config.js module.exports = { plugins: [ require('autoprefixer') ] } // .browserslistrc > 1% not ie { portfinder.getPort({ port: 1081, // 默认1081端口,若被占用,重复+1,直到找到可用端口或到stopPort才停止 stopPort: 65535, // maximum port },(err, port) => { if(err) { reject(err) return } devWebpackConfig.devServer.port = port resolve(devWebpackConfig); }); }) 复制代码 缓存 1. cache

缓存之前打包的内容,配置之后会生成一个.cash文件夹,通过文件缓存,直接缓存到本机磁盘

// //webpack.dev.js cache: { type: 'filesystem', //默认缓存到 node_modules/.cache/webpack。还可以使用 cacheDirectory选项自定义配置 }, 复制代码 2. babel-loader

babel-loader 自带缓存配置。开启后会将缓存放在node_modules/.cache/babel-loader

// //webpack.dev.js { loader: 'babel-loader', options: { cacheDirectory: true, } } 复制代码 3. cache-loader

使用此loader,可以将其他 loader 的结果缓存到磁盘中,默认路径node_modules/.cache/cache-loader

npm i cache-loader -D 复制代码 { test: /\.css$/, use: [ 'cache-loader', 'style-loader', 'css-loader' ], }, 复制代码 第六部分:webpack生产环境配置 单独提取CSS

在生产环境 CSS 文件应该是单独导出的,这时候就需要 mini-css-extract-plugin

npm i mini-css-extract-plugin -D //将 CSS 提取到单独文件 复制代码

mini-css-extract-plugin 使用的时候需要分别配置 loader 和 plugin,loader 需要放在css-loader 之后代替 style-loader

// webpack.prod.js const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { plugins: [ new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[name].[contenthash:8].css' }) ], module: { rules: [ { test: /\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, options: { publicPath: '../', hmr: false } }, 'css-loader' ] } ] } }; 复制代码 Tree Shaking

webpack5 有更好的 Tree Shaking 处理机制,mode 为 production 时默认开启,也可以手动配置开启。

// webpack.prod.js optimization { usedExports: true, minimize: true // 还有更多配置... } 复制代码 // package.json "sideEffects": [ "*.css" // 也可以设置某些文件不 Tree Shaking ] 复制代码 Scope Hoisting

作用域提升,把几个函数合并成一个函数,减少执行每个新函数,产生的临时执行上下文,减少开销,mode 为 production 时默认开启。

压缩 JS 压缩

如果你使用的是 webpack 5 或以上版本,不需要安装这个插件。webpack 5 自带最新的 terser-webpack-plugin,并在mode 为 'production' 时自动使用。

npm i terser-webpack-plugin -D 复制代码 // // webpack.prod.js const TerserPlugin = require("terser-webpack-plugin"); module.exports = { optimization: { minimize: true, minimizer: [new TerserPlugin()], }, }; 复制代码 HTML 压缩 1. html-webpack-plugin 的 minify

html-webpack-plugin的 minify 选项用于设置html文件的压缩,如果 mode 为 'production' 则会自动开启 minify。并使用 html-minifier-terser 进行压缩,默认配置如下,可参考 html-minifier-terser 的文档按需修改:

{ collapseWhitespace: true, keepClosingSlash: true, removeComments: true, removeRedundantAttributes: true, removeScriptTypeAttributes: true, removeStyleLinkTypeAttributes: true, useShortDoctype: true } 复制代码 2. html-minimizer-webpack-plugin

内部同样使用 html-minifier-terser 来压缩 HTML。webpack5 开始,像压缩类的插件,应该配置在 optimization.minimizer 数组中,方便统一管理。插件使用方式可以参考官方文档。

npm i html-minimizer-webpack-plugin -D 复制代码 // webpack.prod.js const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin"); optimization: { minimize: true, minimizer: [ // For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line // `...`, new HtmlMinimizerPlugin(), ], }, 复制代码 CSS 压缩

在 Webpack5 之前,通常使用 optimize-css-assets-webpack-plugin 这个插件来压缩 CSS 代码。但在其 NPM库,有一段话“For webpack v5 or above please use css-minimizer-webpack-plugin instead.” 所以,按照 css-minimizer-webpack-plugin 的文档,配置 Webpack 的optimization.minimize 即可开启css压缩。

npm i css-minimizer-webpack-plugin -D 复制代码 // webpack.prod.js const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); module.exports = { optimization: { // 告知 webpack 使用 TerserPlugin 或其它在 optimization.minimizer 定义的插件压缩 bundle。 minimizer: [ // `...`, // For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line new CssMinimizerPlugin(), ], }, }; 复制代码 拆分代码 splitChunks

在配置 splitChunks 之前,先来简单了解一下 module、chunk、bundle的概念

module:通过 import、require 语句引入的各类资源。 chunk:chunk 包含一个或多个 module,根据 webpack 的配置拆分出来,例如 splitChunks 就可以拆分出额外的 chunk。 bundle:对 chunk 打包压缩之后的最终文件,一般和 chunk 是一对一的关系,也可能会有多个chunk 中的公共部分被组合到一个bundle。 Webpack4 开始,用于处理公共模块的 splitChunks 功能是开箱即用的,默认配置如下: module.exports = { // ... optimization: { splitChunks: { chunks: 'async', // "initial" | "all" | "async" ,默认async minSize: 20000, // 20K,当超过这个体积就会被抽离成公共部分。 minRemainingSize: 0, // 确保拆分后剩余的最小 chunk 体积超过限制来避免大小为零的模块。 minChunks: 1, // 拆分前必须共享模块的最小 chunks 数。 maxAsyncRequests: 30, // 按需加载时的最大并行请求数。例如 import('./page.js'),则引入page.js时,算上page.js本身在内的最大请求数量限制为30个。 maxInitialRequests: 30, // entry入口的最大并行请求数。入口文件本身算一个请求,入口文件内的动态加载模块不计算请求数。 enforceSizeThreshold: 50000, // 强制执行拆分的体积阈值和其他限制(minRemainingSize,maxAsyncRequests,maxInitialRequests)将被忽略。 cacheGroups: { defaultVendors: { test: /[\\/]node_modules[\\/]/, // 符合规则则提取 chunk priority: -10 // 优先级,当一个模块属于多个 chunkGroup,权重值高的优先 reuseExistingChunk: true, // 如果该chunk包含的modules都已经另一个被分割的chunk中存在,那么直接引用已存在的chunk,不会再重新产生一个 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true, } } } } }; 复制代码

在实际项目中,通过 webpack-bundle-analyzer 等 bundle 分析工具,可以分析并将体积过大的公共模块拆分出来。例如发现下图的 moment 包过大,就可以添加配置,拆分出来(当然也可以使用体积更小的 day.js 或者使用 moment-locales-webpack-plugin 插件缩小 moment 体积)。

100878.png

// webpack.prod.js optimization: { splitChunks: { cacheGroups: { moment: { test: /[\\/]node_modules[\\/][email protected]@moment[\\/]/, name: 'moment', chunks: 'all', }, }, }, }, 复制代码

如果使用了 html-webpack-plugin,需要添加 chunks 配置来自动引入拆分出的 chunk。

new HtmlWebpackPlugin({ // ... chunks: ['moment','main'], }), 复制代码 最后

好了,以上就是本篇文章的全部内容了,还有许多常用配置和 Webpack5 新特性没有使用到,提供的选项也存在很大的封装优化空间,当然你也可以编写自己的 loader 和 plugin 来解决遇到的实际问题。本文仅作为学习记录,起一个抛砖引玉的作用,希望文中内容能你有所帮助,谢谢。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有