Vue 项目 build 流程解析(webpack工具解析)

注:本篇文章解析框架为 vue2.0

本篇文章通过解析简单的项目打包步骤试着去了解我们的 Vue 项目是怎么打包的。

build.js 干了什么

首先我们贴上 build.js 代码,方便后续解读:

'use strict'
// 版本校验解析
require('./check-versions')()process.env.NODE_ENV = 'production'
// 引用解析
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')// 执行build解析
const spinner = ora('building for production...')
spinner.start()rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {if (err) throw errwebpack(webpackConfig, (err, stats) => {spinner.stop()if (err) throw errprocess.stdout.write(stats.toString({colors: true,modules: false,children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.chunks: false,chunkModules: false}) + '\n\n')if (stats.hasErrors()) {console.log(chalk.red('  Build failed with errors.\n'))process.exit(1)}console.log(chalk.cyan('  Build complete.\n'))console.log(chalk.yellow('  Tip: built files are meant to be served over an HTTP server.\n' +'  Opening index.html over file:// won\'t work.\n'))})
})

从上述代码备注中,我们将代码分开三部解析,分别为:版本校验解析、引用解析、项目打包

版本校验

执行的第一项就是版本校验,我们打开代码看看校验里面做了什么事情

'use strict'
const chalk = require('chalk')
const semver = require('semver')
const packageConfig = require('../package.json')
const shell = require('shelljs')

首先是引入工具及配置,作用分别为:

  • chalk:能打印颜色信息的插件,主要用来打印一些重要信息
  • semver:语义化版本工具,在本文件中主要做版本提出及当前版本是否符合版本规则校验,详细用途可参照语义化版本控制模块-Semver
  • packageConfig: package 配置内容,本文件中主要获取 node 版本与 npm 版本
  • shelljs:shelljs 是脚本语言解析器,可以调用其中的方法做到执行底层操作命令,比如 shell.cd('lib'); 进入 lib 目录,本文件中主要校验 npm 环境

接下来是一个工具方法:

// 执行命令并返回执行结果
function exec (cmd) {return require('child_process').execSync(cmd).toString().trim()
}

初始化获取 node 信息及 npm 信息

const versionRequirements = [{name: 'node',currentVersion: semver.clean(process.version), // 提取当前node版本versionRequirement: packageConfig.engines.node // 提取框架版本要求}
]// 检查是否有npm运行环境
if (shell.which('npm')) {versionRequirements.push({name: 'npm',currentVersion: exec('npm --version'), // 获取当前npm版本versionRequirement: packageConfig.engines.npm // 获取框架版本要求})
}

最后执行我们的校验方法:

module.exports = function () {// 填充报错内容的数组const warnings = []// 循环校验(其实只有npm与node)当前环境版本与要求版本是否匹配for (let i = 0; i < versionRequirements.length; i++) {const mod = versionRequirements[i]// 判断当前环境版本是否符合项目版本要求,如果不符合,我们将报错提示放入报错数组中if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {warnings.push(mod.name + ': ' +chalk.red(mod.currentVersion) + ' should be ' +chalk.green(mod.versionRequirement))}}// 判断当前报错数组是否有内容(是否有环境不符合项目要求),如果有,打印报错内容并退出本次进程if (warnings.length) {console.log('')console.log(chalk.yellow('To use this template, you must update following to modules:'))console.log()for (let i = 0; i < warnings.length; i++) {const warning = warnings[i]console.log('  ' + warning)}console.log()process.exit(1)}
}

总的来说,check-version.js 只做了一件事,就是校验 npmnode 环境是否符合项目标准。

当然,如果我们想加一些其他的校验也可以,比如我们在打包前要求 webpack 版本低于 7.19.5 ,我们可以这样配置:

首先我们在 package.json 配置项目要求版本:

"engines": {"node": ">= 6.0.0","npm": ">= 3.0.0","webpack": "< 7.19.5"
},

然后,我们在 check-version.js 中添加校验 webpack 的配置项:

const versionRequirements = [{name: 'node',currentVersion: semver.clean(process.version),versionRequirement: packageConfig.engines.node},{name: 'webpack',currentVersion: exec('npm webpack -v'),versionRequirement: packageConfig.engines.webpack}
]

这时候我们执行打包命令: npm run build 可以看到报错如下:

因为此时我们 webpack 版本是 7.20.5 ,而项目要求版本是小于 7.19.5 所以,编译失败。

引用解析

做完版本校验之后,我们开始执行打包逻辑,首先最重要的事就是改变当前的环境为 production ,这是为了确保有些生产环境不需要打包进去的工具或者逻辑被打包(比如上一篇讲的 mockjs),其次便是一些工具与配置项引入:

// 将当前环境置为生产环境
process.env.NODE_ENV = 'production'const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')// 创建一个动态的打包进度文本打印
const spinner = ora('building for production...')
spinner.start()

这一块引入的工具及配置功能如下

  • ora:这是一个优雅的终端旋转器,简单的来说就是可以在终端显示文本前加一个旋转的棍儿,用来标识当前正在执行状态,缓解使用者等待情绪
  • rimraf:这个就是一个包装 rm -rf 命令的工具,工具使用方法为 rimraf(path, callback)
  • path:处理文件路径工具
  • chalk:这个上面有讲,有颜色的打印插件
  • webpack:打包工具,后面会讲到
  • config:项目配置,里面包含打包路径、是否开启分析图等
  • webpackConfig:webpack 配置,其中包含 webpack 打包入口、出口、插件等

项目打包

最后要执行的便是核心的一步:项目打包,打包工具为 webpack ,其配置及作用我将在后面说明,首先看一下打包代码:

rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {if (err) throw errwebpack(webpackConfig, (err, stats) => {spinner.stop()if (err) throw errprocess.stdout.write(stats.toString({colors: true,modules: false,children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.chunks: false,chunkModules: false}) + '\n\n')if (stats.hasErrors()) {console.log(chalk.red('  Build failed with errors.\n'))process.exit(1)}console.log(chalk.cyan('  Build complete.\n'))console.log(chalk.yellow('  Tip: built files are meant to be served over an HTTP server.\n' +'  Opening index.html over file:// won\'t work.\n'))})
})

打包分为三部:

  • 打包目录下文件删除:使用 rm 方法执行删除命令,删除掉打包目录下面的文件夹
  • 执行打包:webpack 进行打包
  • 打包结果展示:关闭 spinner 展示,打印提示及报错

webpack 配置了什么

首先要说到的是 webpack 是什么?

官网描述:webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

简单地说就是从一个或者多个入口开始,顺藤摸瓜的将所有引用集合然后压缩,在加载界面时能够进行模块式的加载一个 js 文件与其相关资源文件。在 webpack 中也可以配置不同的模块来适配多种语言,例如解析 TypeScriptSass 等,也可以对我们 js 代码进行兼容性处理。

webpack 核心配置如下:

  • Entry:入口,打包入口文件,递归解析依赖的源头
  • Module:模块 ,webpack 模块配置,用于引入工具模块用作语言翻译
  • resolve:模块规则,配置以什么规则寻找模块,配置路径映射等
  • output:输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果

vue 项目中,webpack 打包的配置分为两块: webpack.base.conf.jswebpack.prod.conf.js ,前者为公共配置,后者为打包的配置。

公共配置

路径配置

首先是三个打包路径相关配置:

context: path.resolve(__dirname, '../'),
entry: {app: './src/main.js'
},
output: {path: config.build.assetsRoot,filename: '[name].js',publicPath: process.env.NODE_ENV === 'production'? config.build.assetsPublicPath: config.dev.assetsPublicPath
},
  • context:默认执行启动 webpack 时所在的当前工作目录,配置中所有路径以该路径为基础,当前以项目目录为基础
  • entry:配置模块入口,当前项目入口为 srcmain.js
  • output:该项配置打包后文件存放及读取位置
    • path 为打包位置,默认为 static 目录
    • filename 配置入口文件名,nameentry 对象 key ,例如上面生成 app.js, 如果有多个入口,也会生成多个文件
    • publicPath 为静态资源的路径,默认为 / ,可配置为其他外部地址

依赖解析

resolve 配置项

resolve: {extensions: ['.js', '.vue', '.json'],alias: {'vue$': 'vue/dist/vue.esm.js','@': resolve('src'),}
},

两个配置作用如下:

  • extensions:当我们引入组件demo.vue 时,我们可以 import demo from demo 也可以 import demo from demo.vue 那么系统怎么知道引入的是什么文件呢?那就依赖于这个配置项,在引入的时候,系统会先寻找 demo.js 发现没有之后会寻找 demo.vue 以此按顺序比对
  • alias:该配置项为路径映射,在上述例子中配置项配置两个映射:vue$@ ,当我们要引入src/component/demo.vue,则可写为@/component/demo.vue

文件解析

文件解析的目的有三个:

  • 对于浏览器来说,是无法识别 .vue.less 这种文件的,所以我们在编译的时候需要工具将其转化为.js 文件
  • js 代码在不同浏览器上需要进行兼容性处理
  • 引入 Eslint 执行校验

配置模板分成三块:

  • 匹配文件:通过testincludeexclude 匹配需要解析的文件
  • 解析规则:通过use 配置解析模块数组,也可通过loader 配置一组解析模块,解析顺序从右往左按顺序处理
  • 顺序调整:通过enforce 配置,修改默认执行顺序,可以将一个 loader 执行顺序放置在最前或者最后
module: {rules: [...(config.dev.useEslint ? [createLintingRule()] : []),{test: /\.vue$/,loader: 'vue-loader',options: vueLoaderConfig},{test: /\.js$/,loader: 'babel-loader',include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]}, // babel解析js文件,适配js代码兼容性{test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,loader: 'url-loader',options: {limit: 10000,name: utils.assetsPath('img/[name].[hash:7].[ext]')}}, // 将8KB以下的图片进行base64转换,减少界面加载图片时间{test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,loader: 'url-loader',options: {limit: 10000,name: utils.assetsPath('media/[name].[hash:7].[ext]')}},// 将8KB以下的媒体文件进行base64转换,减少界面加载视频时间{test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,loader: 'url-loader',options: {limit: 10000,name: utils.assetsPath('fonts/[name].[hash:7].[ext]')}}, // 将8KB以下的图标文件进行base64转换,减少界面加载图标文件时间{test: /\.less$/,loader: 'style-loader!css-loader!less-loader'} // 将less文件分别通过less-loader、css-loader、style-loader流水线对less文件进行解析]
},

首先第一项配置的是 ESlint 解析配置,由于 Eslint 配置并不是必须的,所以我们通过配置项判断是否解析,Eslint 配置如下:

const createLintingRule = () => ({// 匹配.js或者.vue文件test: /\.(js|vue)$/,// 解析模块为 eslint-loaderloader: 'eslint-loader',// 将解析顺序排至最前enforce: 'pre',// 解析范围为src文件夹下及test文件夹下include: [resolve('src'), resolve('test')],// 指定错误报告的格式规范options: {formatter: require('eslint-friendly-formatter'),emitWarning: !config.dev.showEslintErrorsInOverlay}
})

第二项为 vue 文件的解析,解析配置比较复杂,这次暂时不做详细说明

其他配置规则已在备注中说明

打包配置

打包配置内容合并了公共配置与打包需要的配置,打包配置主要为 Plugins 扩展功能,所以其他配置暂不做介绍

webpack.DefinePlugin

配置编译时的全局常量

new webpack.DefinePlugin({'process.env': env
}),

uglifyjs-webpack-plugin

配置 js 文件打包压缩,将 js 解析压缩成为浏览器可识别的小文件

new UglifyJsPlugin({// uglify配置项,配置是否压缩为falseuglifyOptions: {compress: {warnings: false}},// 是否将错误信息映射到模块(设置为true将会减慢编译速度)sourceMap: config.build.productionSourceMap,// 使用多进程提高构建速度parallel: true
}),

extract-text-webpack-plugin

在没有该项配置打包时,css 将会被打包到 js 文件中,这样我们更新 css 的时候,也会再编译 js ,反之也是相同,所以我们需要将编译后的 cssjs 分离开来(感觉这个配置对于生产环境并没有优化点)

new ExtractTextPlugin({// 生产的css文件名filename: utils.assetsPath('css/[name].[contenthash].css'),// 支持提取异步引入的cssallChunks: true,
}),

optimize-css-assets-webpack-plugin

这个工具主要是做 css 压缩用的

new OptimizeCSSPlugin({cssProcessorOptions: config.build.productionSourceMap? { safe: true, map: { inline: false } }: { safe: true }
}),

html-webpack-plugin

这个工具主要是去创建一个入口的 html 文件,文件会引入 webpack 打包生成的 output 文件,如果有多个 output 也会引入多个文件,配置及作业如下所示

new HtmlWebpackPlugin({filename: config.build.index, // 文件路径及名称template: 'index.html', // 本地模板文件位置,这里引入项目内index.htmlinject: true, // 向template注入所有静态资源,项目中将打包后生成所有的js都引入进index.html// 传递 html-minifier 选项给 minify 输出minify: {removeComments: true, // 去除html注释collapseWhitespace: true, // 折叠文本节点中的空白,如果置为false,html文件将会缩进显示removeAttributeQuotes: true // 尽可能删除属性周围的引号// more options:// https://github.com/kangax/html-minifier#options-quick-reference},// necessary to consistently work with multiple chunks via CommonsChunkPluginchunksSortMode: 'dependency' // thunk插入到html的排列顺序,此处规定按照CommonsChunkPlugin规则排序
}),

webpack.HashedModuleIdsPlugin

这个工具是为了解决打包污染问题,当我们只修改部分文件时,vue 会将所有文件都重新编译一次,这样每次编译代码量会变大,浏览器需要重新获取所有的静态文件,而 webpack.HashedModuleIdsPlugin 以模块相对路径生成 hash 作为模块 id ,做到只更新变动的代码

new webpack.HashedModuleIdsPlugin()

webpack.optimize.ModuleConcatenationPlugin

这个工具能够预编译所有模块到一个闭包中,以提升代码在浏览器中执行速度

new webpack.optimize.ModuleConcatenationPlugin()

webpack.optimize.CommonsChunkPlugin

该模块主要用作公共模块的拆分独立,在进入系统的时候最开始加载一次,后续都从缓存中获取,减少大量的静态文件获取,提高加载界面速度,下面是项目内拆分规则:

// split vendor js into its own file
// 将node_modules内容拆分到vendor中
new webpack.optimize.CommonsChunkPlugin({name: 'vendor',minChunks (module) {// any required modules inside node_modules are extracted to vendorreturn (module.resource &&/\.js$/.test(module.resource) &&module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0)}
}),
// 该模块与上述HashedModuleIdsPlugin共同作用避免公共chunk改变
new webpack.optimize.CommonsChunkPlugin({name: 'manifest',minChunks: Infinity
}),
// 从代码中提取共享模块,并将其绑定在一个单独的模块中
new webpack.optimize.CommonsChunkPlugin({name: 'app',async: 'vendor-async',children: true,minChunks: 3
}),

copy-webpack-plugin

该工具执行打包流程的最后一步,将打包好的文件赋值到目标目录中:

new CopyWebpackPlugin([{from: path.resolve(__dirname, '../static'),to: config.build.assetsSubDirectory,ignore: ['.*']}
])

compression-webpack-plugin

该工具为可选工具,主要作用为准备资源的压缩版本以通过 Content-Encoding 为其提供服务,默认关闭,可在 config/index.js 中打开:

webpackConfig.plugins.push(new CompressionWebpackPlugin({asset: '[path].gz[query]',algorithm: 'gzip',test: new RegExp('\\.(' +config.build.productionGzipExtensions.join('|') +')$'),threshold: 10240,minRatio: 0.8})
)

注意,开启该功能需要下载组件,由于高版本不兼容,建议下载老版本组件:

npm install --save-dev compression-webpack-plugin@1.1.12

webpack-bundle-analyzer

该工具为可视化资源分析工具,能够比较详细的知道那一块代码内存占用较大,通常用于发布包大小优化:

if (config.build.bundleAnalyzerReport) {const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPluginwebpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

如果想要看到分析图,需要执行以下命令:

npm run build --report

执行后就可以看到分析图了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZQ25bGar-1639550253515)(./image/analyzer.png)]

有关 vue 项目的 build 分析基本完成,有不对的地方还请多多指正。

threshold: 10240,
minRatio: 0.8
})
)


注意,开启该功能需要下载组件,由于高版本不兼容,建议下载老版本组件:```shell
npm install --save-dev compression-webpack-plugin@1.1.12

webpack-bundle-analyzer

该工具为可视化资源分析工具,能够比较详细的知道那一块代码内存占用较大,通常用于发布包大小优化:

if (config.build.bundleAnalyzerReport) {const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPluginwebpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

如果想要看到分析图,需要执行以下命令:

npm run build --report

执行后就可以看到分析图了:

有关 vue 项目的 build 分析基本完成,有不对的地方还请多多指正。

Vue 项目 build 流程解析(webpack工具解析)相关推荐

  1. Vue 项目搭建流程和使用大全

    Vue 项目搭建流程及项目中遇到的问题 项目搭建流程 1.使用vue cli 搭建项目框架 cnpm install -g vue-cli 安装vue cli 脚手架 vue init webpack ...

  2. Vue进阶(幺捌柒):vue项目build报错的解决办法(ERROR in static/js/***.js from UglifyJs)

    文章目录 一.前言 二.问题分析 三.问题解决 四.拓展阅读 一.前言 Vue项目编译过程中,出现如下错误信息: ERROR in static/js/vendor.f1c68aa2d5e85847d ...

  3. 初始化一个vue项目的流程

    为什么80%的码农都做不了架构师?>>>    一.安装 nodejs 建议安装 v 7.9.0 本人使用的此版本比较好 二.安装 安装vue.创建vue项目 # 全局安装 vue- ...

  4. Vue项目build后静态资源文件路径或新建文件夹图片路径找不到的问题

    问题描述:使用vue-cli脚手架工具生成的vue项目,使用npm run build后生成的文件直接双击打开白屏一片. 解决方案: 第一步:修改build文件夹下utils.js,在以下位置加入 i ...

  5. vue项目调用通用组件_详细解析:uniapp项目|vue组件形式实现的科技感loading纯CSS动效...

    前言 本人是一枚并不安分守己的后端程序猿,一直对前端开发"垂涎三尺",所以,一有机会就会"不务正业"一番.最近,发现了一个非常好的学习资料,于是乎,我的老毛病又 ...

  6. 离职后才搞懂vue项目开发流程中的疑惑点

    在离职的最后一个月,帮两位同事申请加薪,确切的说,申请加薪是导火索,我被扣上了哄抬同事工资以提高自己工资的帽子,在推动前后端分离工作中处处碰壁,点燃了压抑许久的离职冲动,领导培养自己四五年,不让声张, ...

  7. 搭建SpringBoot+Vue 项目 完整流程

    文章目录 一.创建后端 SpringBoot 项目 二.Vue 创建前端项目 三.SpringBoot 集成 MyBatis 四.前后端联调 一.创建后端 SpringBoot 项目 创建过程 略 创 ...

  8. 解决vue项目build的时候报错Warning: Accessing non-existent property ‘cat‘ of module exports inside circular de

    *  正在执行任务: npm run build > selection-tool@1.0.0 build > node build/build.js - building for pro ...

  9. 【前端】Vue项目开发流程

    项目初始化 安装Vue 脚手架 npm install -g @vue/cli 通过Vue脚手架创建项目 vue ui i.创建-选择路径; ii.输入项目名称-git初始化信息; iii.预设(手动 ...

最新文章

  1. python获取耗时的shell_python获取耗时的shell_关于Linux:在shell中获取程序执行时间...
  2. EDM营销中HTML邮件设计方法和技巧
  3. Ubuntu 11.04上搭建Android开发环境
  4. 深度剖析:CDN内容分发网络技术原理--转载
  5. 【职场建议】开发转算法,我们应该如何准备(过来人的肺腑之言)
  6. html表单ui图片,semantic-ui 表单(示例代码)
  7. EF Core 小坑:DbContextPool 会引起数据库连接池连接耗尽
  8. ZooKeeper未授权访问漏洞记录(影响范围:全版本,端口:2181)
  9. [Unity] ACT 战斗系统学习 4:重构前的第三人称控制器
  10. lssvm回归 matlab,lssvm回归预测的程序运行不了 求高手修改指点
  11. leecode 树是否是平衡树 java
  12. 转载 Log4j2在WEB项目中配置
  13. Dxg——Raspberry Pi Pico python 开发笔记整理分类合集【所有的相关记录,都整理在此】
  14. 算法与数据结构(基于C语言)中线性表的快速排序快速查找
  15. linux如何卸载金山安全终端,卸载和释放-文档中心-金山云
  16. Woff2字体404错误
  17. Maxwell参数化建模
  18. DuckDuckGo将与整合Apple Maps有更丰富的地图信息及隐私
  19. 分享10个最佳Linux VPS服务器托管
  20. 3个APP海外推广方式,不走寻常路

热门文章

  1. 解释某宝的一段混淆视听的代码
  2. 查看硬盘缓存linux,Linux如何查看硬盘型号和缓存
  3. 017 《你不理财财不理你(畅销十年纪念版)》听后感
  4. 重庆网通信息港试点电力线上网
  5. 播放器播放视频画面均变暗(但网页视频正常)的解决方案
  6. 广东话轻松学习[二]
  7. Python degrees() 函数
  8. 3389端口号被攻击,该如何修改?
  9. 智能电表怎么实现远程抄表
  10. 关于商业/数据分析,很多人不知道的四大进阶能力