first thing fitrst 博主声明:绝对不当标题党
有人看最好不过的背景:

  十月初对公司产品的前端构建做了一些优化,但还遗留了不少问题(可了解我的前一篇博文:一次webpack小规模优化经历 https://www.cnblogs.com/byur/p/13977657.html),这里姑且列了一个表出来记录当前这个版本的不足:

  1.热重载过慢:单文件改动,热重载的十次平均响应时间约为17s,严重影响开发体验;

  2.某些bundle体积过大,导致单个资源请求耗时过多,浏览器加载速度收到影响;

  3.没有liint机制去控制编码过程中的语法规范,也没做代码保存的自动格式化,代码质量低,组员编码风格迥异、交接成本高。

  4.打包体积与打包速度仍有优化空间。

  综合考虑以上问题后,个人判断webpack1的性能不足以为前端项目的构建流程提供更好的支持,遂决定把webpack升级到更高版本,用新特性与更强的性能,改善构建体验,造福运维与测试同事hhhh。

这次基本没图的正文:

  本系列博文将演示如何将webpackv1.x(1.13.2)升级到v4.x(4.44.2),选择这个小版本的原因是因为它是webpack4的最新一个小版本(2020.11),webpack4从发布测试版本到现在为止已经有两年多了,两年里的迭代和bug修复,足够让这个大版本的功能变得完善和稳定到让人信任的程度。至于升级的手法,我认为在原来配置的基础上做修改逐步升级,极有可能会被原来的写法误导,导致浪费时间,所以这次升级过程中我换了一种思路,具体的做法是做备份之后删除原来的配置文件,从零开始进行升级,因此本文兴许也可以当作一个用webpack构建项目的入门教程。

  package.json里有个devDependencies,记录了项目在开发环境下需要的依赖,在做好文件备份后,我将node_modules删除,将devDependencies的列表清空;然后npm i。

  然后我开始实现一个最简化的版本,我装上了4.x版本最新的webpack:

npm i webpack@4.44.2 -D

  webpack4.x版本需要命令行工具才能运行,所以我们还需要去下载webpack-cli,我就随便选了一个不算新也不算旧的版本:

npm i webpack-cli@3.3.9 -D

  然后开始写配置文件,首先写一个基本版的测试一下新版本webpack的可行性:

  先创建一个简单的入口文件test.js供打包用:

1 import {cloneDeep} from "lodash"
2 const obj = {color:'red'}
3 const copy = cloneDeep(obj)

  在项目根目录下创建webpack.config.js文件:

1 const path = require('path');
2 module.exports = {
3 entry: "./src/test.js",
4 output: {
5 path: path.resolve(__dirname,"dist"),
6 filename: 'testbundle.js',
7 }
8 }

  webpack启动时,如果未指定运行的文件,就会自动读取根目录下的webpack.config.js中的配置,现在修改package.json的scripts中的build命令:

  "scripts": {
"build": "webpack"
},

  运行npm run build,webpack便会按配置进行打包,然后你会看到你的dist目录中多出一个名为testbundle.js的文件。

  同时,按照我这个配置,会在控制台到看到一个警告:

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

  这个警告表示,当前传给webpack的配置中没有设置“mode”属性,对此webpack将视作mode: "production"来处理,这里需要提到webpack4配置文件中的mode属性,webpack4内置了一些比较通用的插件配置,省去了开发者为配置webpack而消耗的时间精力,使用mode属性就可以快捷配置这两套插件,mode:"development"跟mode:"production"分别就对开发环境与生产环境两种场景做了优化,比如持久化缓存、代码压缩等,如果你不想使用这两种预设的任意一种,可以将mode的值设为"none"。至于其他细节,感兴趣的朋友可以从文档获取更多信息:https://www.webpackjs.com/concepts/mode/

  刚才演示的配置文件,灵活性与性能都远远达不到真实工作场景的需求,只能称作玩具,所以接下来你将接触更具体也更接近实际场景的配置。

  现在,我们把配置文件改一改,常规的思路是将开发环境的配置跟产品环境的配置分离成两个文件,一般命名为webpack.dev.conf.js和webpack.prod.conf.js,因为这两个场景下的配置都有部分共同之处,所以又可以抽出一个公共的配置文件webpack.base.conf.js,目前我们先不去考虑生产环境与开发环境下的差异,先创建一个基本配置webpack.base.js,让webpack能够正确地解析一个vue文件,顺利完成打包。

  首先解析.vue文件,需要安装vue-loader以及与vue同版本号的vue-template-loader,这里需要注意的是vue-loader版本如果在15及以上,需要额外从vue-loader的目录里引入VueLoaderPlugin,VueLoaderPlugin将使用你在rules中定义的其他规则来检查和处理.vue文件中符合规则的语句块

const VueLoaderPlugin = require("vue-loader/lib/plugin");
...
...
...
module: {
  rules: [
    {
      test: "/\.vue$/"
      loader: "vue-loader"
    }
  ]
}, plugins: [
new VueLoaderPlugin()
]

  

  如果这时候你已经看到本文的更下面并且写好了build文件,或者是在webpack.config.js的基础之上改写配置文件,此时执行打包命令你将会发现控制台输出了很多错误,例如:

  

  满屏的红字有些吓人,但仔细看看就会发现其实并不是什么大不了的问题,截图上有一段样式代码,并且报错提示你可能需要其他loader去处理vue-loader的解析结果,所以为了解决截图上的问题能,使webpack能够顺利对样式代码进行处理,你需要添加相应的loader,添加什么由你的项目具体使用情况决定:

npm i css-loader style-loader url-loader file-loader less less-loader sass node-sass stylus stylus-loader -D

  css-loader用于解析css代码,style-loader则生成style标签将css挂载在到页面结构中,file-loader读取静态资源的引用路径,在输出目录中生成符合规则的文件,供编译后的代码使用,url-loader在file-loader的基础之上,将体积小于指定数值的文件转码成base64字符串,可通过这种方式减少资源请求数。其他文件其他loader以及相关依赖不再赘述。

  

  css相关loader的载入我沿用了项目之前的写法(反正也是从别的地方抄来的),稍微加了些改动:

exports.cssLoaders = function () {

  // style-loader改为使用vue-style-loader,除了具备与style-loader一样的功能之外,还实现了不需要页面刷新的样式层面的热重载(来自vue-laoder官网描述)

  const vueStyleLoader = {
loader: "vue-style-loader"
}
const cssLoader = {
loader: "css-loader",
   // 如果你使用的是vue-style-loader并且css-loader的版本在v4.0.0及以上,这个属性需要加上,具体原因请看https://www.cnblogs.com/byur/p/14194672.html
   
   options: {
   esModule: false
   }
  }
// 当在一条规则中应用多个loader时,loader的执行顺序从右至左,所以预处理语言相关的loader摆右边 
 // 如果generateLoaders没有接收到参数,将以返回基础的loader配置:使用css-loader与vue-style-loader
  function generateLoaders (loader) {
const outputLoaders = [vueStyleLoader,cssLoader]
if (loader) {
const targetloader = {loader:loader+"-loader"}
outputLoaders.push(targetloader)
}
return outputLoaders
}

return {
css: generateLoaders(),
less: generateLoaders("less"),
sass: generateLoaders("sass"),
scss: generateLoaders("sass"),
stylus: generateLoaders("stylus"),
styl: generateLoaders("stylus")
}
}
exports.styleLoaders = function () {
var output = []
var loaders = exports.cssLoaders() for (let extension in loaders) {
var loader = loaders[extension]
console.log(loader)
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}

  

  这个丐版的styleLoaders输出了一个保存了处理样式文件规则的数组,你可以使用拓展运算符将这些规则挂载到rules中。所以接下来我们创建一个build.js,用这个文件调起webpack的api进行打包。我比较倾向于这种写法,用命令行调用node执行一个build文件,在这个文件中运行webpack,这样写在处理不同打包配置的场景时要稍微方便一些,比如有的公司就分sit、uat、prod(生产)等好几套环境,会对应不同的全局配置(如接口的的baseurl、请求加解密、局部打包等等),这种情况下可以通过process.argv来获取命令行参数,细化配置;对于我来说另外一个好处是方便加old_space参数给内存扩容,这样能避免一些稍大的项目运行过程中出现内存不够导致编译失败的问题(64位windows给node分配的内存大概是1.4G)

process.env.NODE_ENV = 'production'

var webpack = require('webpack')
var webpackConfig = require('./webpack.prod.conf') webpack(webpackConfig, function (err, stats) {
// spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n')
})

丐版build.js

  

  把build命令改写为:

  

"build": "node --max_old_space_size=2077 build/build.js"

  现在执行npm run build,看看会发生什么:

  

  过程没报错,一个基础的打包流程,到现在其实就走完了,这个包实际上也不能用,但我认为基础篇的意义在于引导读者顺利完成第一步,在这个前提之上进行功能的丰富,这样的话无论是操作失误回退代码或者是对优化方向的梳理都有一定的积极意义。

  基础篇到这里就该结束了,在进阶篇,我将展示一个完成度更高的版本。

  

  附:

var path = require('path')
var config = require('../config')
var utils = require('./utils')
const VueLoaderPlugin = require('vue-loader/lib/plugin') module.exports = {
mode: "none",
entry: "./src/module/indexApp/index.js",
output: {
path: config.build.assetsRoot,
publicPath: config.build.assetsPublicPath,
filename: utils.assetsPath('js/[name][hash].js'),
chunkFilename: utils.assetsPath('js/[id][chunkhash].js')
},
resolve: {
extensions: ["*",'.js', '.vue'],
alias: {
'vue$': 'vue/dist/vue',
'src': path.resolve(__dirname, '../src'),
'common': path.resolve(__dirname, '../src/common'),
'components': path.resolve(__dirname, '../src/components'),
'components2': path.resolve(__dirname, '../src/components2'),
'module': path.resolve(__dirname, '../src/module'),
'config': path.resolve(__dirname, '../src/config'),
'library': path.resolve(__dirname, '../src/library'),
'jsplumb': path.resolve(__dirname, '../src/library/jsplumb.js'),
'echarts-wordcloud': path.resolve(__dirname, '../src/library/echarts-wordcloud')
}
},
module: {
rules: [
...utils.styleLoaders(),
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.(cur|png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
exclude: [
path.resolve(__dirname, '../src/components/icon'),
],
query: {
limit: 10,
name: utils.assetsPath('img/[name].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[ext]')
}
}
]
},
plugins: [
new VueLoaderPlugin()
]
}

webpack.base.conf.js

var path = require('path')
var config = require('../config') exports.assetsPath = function (_path) {
var assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory;
return path.posix.join(assetsSubDirectory, _path)
} exports.cssLoaders = function () {
const vueStyleLoader = {
loader: "vue-style-loader"
}
const cssLoader = {
loader: "css-loader",
}
// loader解析顺序从右至左
// const baseLoaders = [vueStyleLoader,cssLoader]
function generateLoaders (loader) {
const outputLoaders = [vueStyleLoader,cssLoader]
if (loader) {
const targetloader = {loader:loader+"-loader"}
outputLoaders.push(targetloader)
}
return outputLoaders
} return {
css: generateLoaders(),
less: generateLoaders("less"),
sass: generateLoaders("sass"),
scss: generateLoaders("sass"),
stylus: generateLoaders("stylus"),
styl: generateLoaders("stylus")
}
}
exports.styleLoaders = function () {
var output = []
var loaders = exports.cssLoaders() for (let extension in loaders) {
var loader = loaders[extension]
console.log(loader)
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}

utils.js

process.env.NODE_ENV = 'production'

var webpack = require('webpack')
var webpackConfig = require('./webpack.base.conf') webpack(webpackConfig, function (err, stats) {
// spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n')
})

build.js

  

基于vue2.x的webpack升级与项目搭建指南--基础篇的更多相关文章

  1. 从零搭建基于webpack的Electron-Vue3项目(1)——基于webpack的Vue3项目搭建

    从零搭建基于webpack的Electron-Vue3项目(1)--基于webpack的Vue3项目搭建 前言 本篇文章内容,主要是基于webpack的Vue3项目开发环境进行搭建,暂时还不涉及到El ...

  2. 基于webpack的React项目搭建(一)

    前言 工欲善其事,必先利其器.为了更好的学习React,我们先简要的把开发环境搭建起来.本文主要介绍使用webpack搭建React项目,如果你对React或es6的基础语法还不了解,建议先去学习学习 ...

  3. 基于webpack的React项目搭建(二)

    前言 前面我们已经搭建了基础环境,现在将开发环境更完善一些. devtool 在开发的过程,我们会经常调试,so,为了方便我们在chrome中调试源代码,需要更改webpack.config.js,然 ...

  4. 【基于WinForm+Access局域网共享数据库的项目总结】之篇三:Access远程连接数据库和窗体打包部署

    篇一:WinForm开发总体概述与技术实现 篇二:WinForm开发扇形图统计和Excel数据导出 篇三:Access远程连接数据库和窗体打包部署 [小记]:最近基于WinForm+Access数据库 ...

  5. 基于Vue2写的一个有关美食项目

    刚学Vue练习的一个项目 使用Vue2+vue-router+vuex+axios+webpack router使用了默认的hash模式 引入了高德地图和element-ui 项目地址点击这里 演示地 ...

  6. 基于webpack的React项目搭建(三)

    前言 搭建好前文的开发环境,已经可以进行开发.然而实际的项目中,不同环境有着不同的构建需求.这里就将开发环境和生产环境的配置单独提取出来,并做一些简单的优化. 分离不同环境公有配置 不同环境虽然有不同 ...

  7. 基于Vue2.x的小米商城移动端项目

    初学vue已经有一段时间,为了检验自己的学习成果,决定做一个项目作为一个阶段性总结,项目花了差不多半个月时间,目前实现了7个页面,商城的主要功能基本实现,代码已经放到github上面. 这个项目把大部 ...

  8. 【基于WinForm+Access局域网共享数据库的项目总结】之篇一:WinForm开发总体概述与技术实现

    篇一:WinForm开发总体概述与技术实现 篇二:WinForm开发扇形图统计和Excel数据导出 篇三:Access远程连接数据库和窗体打包部署 [小记]:最近基于WinForm+Access数据库 ...

  9. 【基于WinForm+Access局域网共享数据库的项目总结】之篇二:WinForm开发扇形图统计和Excel数据导出

    篇一:WinForm开发总体概述与技术实现 篇二:WinForm开发扇形图统计和Excel数据导出 篇三:Access远程连接数据库和窗体打包部署 [小记]:最近基于WinForm+Access数据库 ...

随机推荐

  1. qtp学习入门

    qtp的学习,初始入门是简单的,推荐田艳琴的<QTP从实践到精通>这边书,看过后,一周就可以入门,并能够自行编写脚本,但是想要进入更深一层,则需要更广阔的知识!这条路任重道远,你我共勉

  2. Codeforces Edu Round 63 A-E

    A. Reverse a Substring 容易看出,只要符合递增顺序就符合\(NO\),否则则可以找到一组,每次记录最大值比较即可. #include <cstdio> #includ ...

  3. 关于大视频video播放的问题以及解决方案(m3u8的播放)

    在HTML5里,提供了<video>标签,可以直接播放视频,video的使用很简单: <video width="320" height="240&qu ...

  4. Azure应用服务+Github实现持续部署

    上次我们介绍了如何使用Azure应用服务(不用虚机不用Docker使用Azure应用服务部署ASP.NET Core程序).我们通过Visual studio新建一个项目后手动编译发布代码.然后通过F ...

  5. mysql 8.0 改变数据目录和日志目录(一)

    一.背景 原数据库数据目录:/data/mysql3306/data,日志文件目录:/data/mysql3306/binlog 变更后数据库目录:/mysqldata/3306/data,日志文件目 ...

  6. ORA-01578: ORACLE data block corrupted (file # 3, block # 1675)

    警告日志中发现如下报错信息: ORA-01578: ORACLE data block corrupted (file # 3, block # 1675)ORA-01110: data file 3 ...

  7. MySQL03-多表&事务

    1.多表查询 1.1 笛卡尔积 有两个集合A,B .取这两个集合的所有组成情况. 要完成多表查询,需要消除无用的数据 1.2 多表查询分类 1.2.1 内连接查询: 1.隐式内连接:使用where条件 ...

  8. Idea中Web项目Jsp文件找不到类解决方法

    在src下创建package,java代码放到包中,编译时才能在WEB-INFO的classes文件夹中生成可识别的class文件 https://blog.csdn.net/youwanname/a ...

  9. Java进阶:基于TCP通信的网络实时聊天室

    目录 开门见山 一.数据结构Map 二.保证线程安全 三.群聊核心方法 四.聊天室具体设计 0.用户登录服务器 1.查看当前上线用户 2.群聊 3.私信 4.退出当前聊天状态 5.离线 6.查看帮助 ...

  10. JDK 8 新特性,从入门到精通

    default关键字 在jdk1.8以前接口里面是只能有抽象方法,不能有任何方法的实现的. 在jdk1.8里面打破了这个规定,引入了新的关键字:default,使用default修饰方法,可以在接口里 ...