关键词:
作为一名使用了一段时间Vue.js的新手,相信和不少初入Vue的朋友一样,都对Vue-cli的配置一知半解。后来通过对webpack的学习,也算是对脚手架的配置有了一定的了解,所以也想把这段时间自己的成果分享给大家,希望能和大家一起进步。
有两点要说明的:
- 阅读本文需要了解一点点webpack的知识,至少要entry,output,module,plugins都是做什么,以及一些常用的loader和plugins;
- 本文使用的是最新版的vue,配置可能会和大家的有所不同,不过差距不会太大,不影响阅读;
一.起步
先放一张自己整理的简易脑图:
Vue-cli有两个文件——build和config:build文件包含了脚手架在开发环境和生产环境下webpack该如何配置。config文件则包含了build文件下webpack具体配置的值。换句话说,build下的webpack配置的值要引入config后才能获取到。
config文件夹下一共有三个文件:
- dev.env.js: 导出开发环境名称;
- prod.env.js: 导出生产环境名称;
- index.js: 导出不同环境的具体配置;
build文件夹下一共有七个文件:
- build.js: 编译时的入口文件,当执行npm run build时其实就是执行node build/build.js(在package.json中);
- check-versions.js: 编译代码时执行的确认node和npm版本的文件,如果版本不符,则停止编译;
- utils.js:这个文件有两个作用,一是作为vue-loader的配置来使用;另一个是用来给开发环境和生产环境配置loader;
- vue-loader.conf.js:vue-loader的配置,用在webpack.base.conf.js中;
- webpack.base.conf.js:vue-cli脚手架的基础webpack配置,通过与webpack.dev.conf.js和webpack.prod.conf.js两个配置文件的合并(合并方式我会在下一章来讲)来实现“不重复原则(Don\'t repeat yourself - DRY),不会在不同的环境中配置相同的代码”。
- webpack.dev.conf.js:开发环境下的webpack的配置;
- webpack.prod.conf.js:生产环境下的webpack的配置;
二.config文件
1.prod.env.js:
//导出一个对象,对象有一个当前node环境的属性,值为“production”(生产环境) module.exports = NODE_ENV: \'"production"\'
2.dev.env.js:
//导出另一个对象,属性为当前的node环境,值为“development”(开发环境) const merge = require(\'webpack-merge\')const prodEnv = require(\'./prod.env\') module.exports = merge(prodEnv, NODE_ENV: \'"development"\')
- 这里要着重说一下webpack-merge这个包,这个包的作用是来合并两个配置文件对象并生成一个新的配置文件,有点儿类似于es6的Object.assign()方法。如果合并的过程中遇到冲突的属性,第二个参数的属性值会覆盖第一个参数的属性值。
- 前面写到webpack.base.conf.js与webpack.dev.conf.js和webpack.prod.conf.js的合并也用到了webpack-merge。Vue-cli将一些通用的配置抽出来放在一个文件内(webpack.base.conf.js),在对不同的环境配置不同的代码,最后使用webpack-merge来进行合并,减少重复代码。
关于更多webpack-merge请点击https://www.npmjs.com/package/webpack-merge
3.index.js:
index.js作为具体的配置值,我觉得没必要把代码贴出来了,大家可以拿上面的的脑图或者自己项目里的文件来结合我后面要说的代码来看。
三.build文件
1.check.versions.js:
//chalk 是一个用来在命令行输出不同颜色文字的包,可以使用chalk.yellow("想添加颜色的文字....") //来实现改变文字颜色的; const chalk = require(\'chalk\') //semver 的是一个语义化版本文件的npm包,其实它就是用来控制版本的; const semver = require(\'semver\')const packageConfig = require(\'../package.json\') //一个用来执行unix命令的包 const shell = require(\'shelljs\') //child_process 是Node.js提供了衍生子进程功能的模块,execSync()方法同步执行一个cmd命令, //将返回值的调用toString和trim方法 function exec (cmd) return require(\'child_process\').execSync(cmd).toString().trim() const versionRequirements = [ name: \'node\', //semver.clean()方法返回一个标准的版本号,切去掉两边的空格,比如semver.clean(" =v1.2.3 ") //返回"1.2.3",此外semver还有vaild,satisfies,gt,lt等方法, //这里查看https://npm.taobao.org/package/semver可以看到更多关于semver方法的内容 currentVersion: semver.clean(process.version), versionRequirement: packageConfig.engines.node ] //shell.which方法是去环境变量搜索有没有参数这个命令 if (shell.which(\'npm\')) versionRequirements.push( name: \'npm\', //执行"npm --version"命令 currentVersion: exec(\'npm --version\'), versionRequirement: packageConfig.engines.npm ) //后面这部分代码就比较好理解了 module.exports = function () const warnings = [] 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)
2.utils.js:
const path = require(\'path\')const config = require(\'../config\') //这个plugin的作用是将打包后生成的css文件通过link的方式引入到html中,如果不适用这个插件css代码会 //放到head标签的style中 const ExtractTextPlugin = require(\'extract-text-webpack-plugin\') const packageConfig = require(\'../package.json\') //process.env.NODE_ENV是一个环境变量,它是由webpack.dev/prod.conf.js这两个文件声明的; //这里的意思是判断当前是否是开发环境,如果是就把config下index.js文件中build.assetsSubDirectory或 //dev.assetsSubDirectory的值赋给assetsSubDirectory exports.assetsPath = function (_path) const assetsSubDirectory = process.env.NODE_ENV === \'production\' ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory //path.posix.join是path.join的一种兼容性写法,它的作用是路径的拼接,这里返回的是"static/_path" return path.posix.join(assetsSubDirectory, _path ) //cssLoaders的作用是导出一个供vue-loader的options使用的一个配置; exports.cssLoaders = function (options) options = options || const cssLoader = loader: \'css-loader\', options: sourceMap: options.sourceMap const postcssLoader = loader: \'postcss-loader\', options: sourceMap: options.sourceMap function generateLoaders (loader, loaderOptions) const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] if (loader) loaders.push( loader: loader + \'-loader\', options: Object.assign(, loaderOptions, sourceMap: options.sourceMap ) ) if (options.extract) return ExtractTextPlugin.extract( use: loaders, fallback: \'vue-style-loader\' ) else return [\'vue-style-loader\'].concat(loaders) return css: generateLoaders(), postcss: generateLoaders(), less: generateLoaders(\'less\'), sass: generateLoaders(\'sass\', indentedSyntax: true ), scss: generateLoaders(\'sass\'), stylus: generateLoaders(\'stylus\'), styl: generateLoaders(\'stylus\') // styleLoaders是用来给webpack提供所有和css相关的loader的配置,它也使用了cssLoaders()方法; exports.styleLoaders = function (options) const output = [] const loaders = exports.cssLoaders(options) for (const extension in loaders) const loader = loaders[extension] output.push( test: new RegExp(\'\\\\.\' + extension + \'$\'), use: loader ) return output //\'node-notifier\'是一个跨平台系统通知的页面,当遇到错误时,它能用系统原生的推送方式给你推送信息 exports.createNotifierCallback = () => const notifier = require(\'node-notifier\') return (severity, errors) => if (severity !== \'error\') return const error = errors[0] const filename = error.file && error.file.split(\'!\').pop() notifier.notify( title: packageConfig.name, message: severity + \': \' + error.name, subtitle: filename || \'\', icon: path.join(__dirname, \'logo.png\') )
这里可能有的朋友不了解cssLoaders()和styleLoaders()这两个方法返回的是个什么东西,我在这里简单的写一下:
- cssLoaders方法根据传进来的参数(options)是否有extract属性来返回不同的值,如果你看了后面的代码你就会知道在生产模式下extract属性为true,开发模式下为false。也就是说,在生产模式下返回的是一个类似于这样的数组:
ExtractTextPlugin.extract( use: ["css-loader","less-loader","sass-loader"...], fallback: \'vue-style-loader\' )
这些css代码打包以link的方式放到HTML中。当然了,use的值确切的说应该是这样:
[ loader: \'css-loader\', options: sourceMap: true , loader: \'less-loader\', options: sourceMap: true ]
我为了方便看就简写了。
而在开发模式下,cssLoaders返回的是:
["vue-style-loader","css-loader","less-loader","sass-loader"...] //我还是简写了
- styleLoaders方法返回的值就简单了,它返回的就是webpack中module里常用的配置格式:
[ test: /\\.css$/, use: [ \'style-loader\', \'css-loader\' ] , ... ]
const utils = require(\'./utils\') const config = require(\'../config\') //不同环境为isProduction 赋值: 生产环境为true,开发环境为false const isProduction = process.env.NODE_ENV === \'production\' //不同环境为sourceMapEnabled 赋值: 这里都为true const sourceMapEnabled = isProduction ? config.build.productionSourceMap : config.dev.cssSourceMap //导出vue-loader的配置,这里我们用了utils文件中的cssLoaders(); module.exports = loaders: utils.cssLoaders( sourceMap: sourceMapEnabled, extract: isProduction ), cssSourceMap: sourceMapEnabled, cacheBusting: config.dev.cacheBusting, //transformToRequire的作用是在模板编译的过程中,编译器可以将某些属性,如src转换为require调用; transformToRequire: video: [\'src\', \'poster\'], source: \'src\', img: \'src\', image: \'xlink:href\'
4.webpack.base.conf.js:
const path = require(\'path\') const utils = require(\'./utils\') const config = require(\'../config\') const vueLoaderConfig = require(\'./vue-loader.conf\') //resolve这个函数返回的是当前目录下"../dir"这个文件夹,__dirname指的是当前文件所在路径 function resolve (dir) return path.join(__dirname, \'..\', dir) module.exports = //返回项目的根路径 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 , resolve: //自动解析扩展,比如引入对应的文件,js,vue,json的后缀名就可以省略了 extensions: [\'.js\', \'.vue\', \'.json\'], alias: //精准匹配,使用vue来替代vue/dist/vue.esm.js \'vue$\': \'vue/dist/vue.esm.js\', //使用@替代src路径,当你引入src下的文件是可以使用import XXfrom "@/xx" \'@\': resolve(\'src\'), , //一些loader配置,避免篇幅过长我省略一部分,大家可以看自己的文件 module: rules: [ test: /\\.vue$/, loader: \'vue-loader\', options: vueLoaderConfig , test: /\\.js$/, loader: \'babel-loader\', include: [resolve(\'src\'), resolve(\'test\'), resolve(\'node_modules/webpack-dev-server/client\')] , ...... ] , //node里的这些选项是都是Node.js全局变量和模块,这里主要是防止webpack注入一些Node.js的东西到vue中 node: setImmediate: false, dgram: \'empty\', fs: \'empty\', net: \'empty\', tls: \'empty\', child_process: \'empty\'
5.webpack.dev.conf.js:
const utils = require(\'./utils\') const webpack = require(\'webpack\') const config = require(\'../config\') const merge = require(\'webpack-merge\') const path = require(\'path\') const baseWebpackConfig = require(\'./webpack.base.conf\') //一个负责拷贝资源的插件 const CopyWebpackPlugin = require(\'copy-webpack-plugin\') const HtmlWebpackPlugin = require(\'html-webpack-plugin\') //一个更友好的展示webpack错误提示的插件 const FriendlyErrorsPlugin = require(\'friendly-errors-webpack-plugin\') //一个自动检索端口的包 const portfinder = require(\'portfinder\') const HOST = process.env.HOSTconst PORT = process.env.PORT && Number(process.env.PORT) const devWebpackConfig = merge(baseWebpackConfig, module: rules: utils.styleLoaders( sourceMap: config.dev.cssSourceMap, usePostCSS: true ) , devtool: config.dev.devtool, // devServer的配置大家看文档就好了 devServer: clientLogLevel: \'warning\', historyApiFallback: rewrites: [ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, \'index.html\') , ], , hot: true, contentBase: false, compress: true, host: HOST || config.dev.host, port: PORT || config.dev.port, open: config.dev.autoOpenBrowser, overlay: config.dev.errorOverlay ? warnings: false, errors: true : false, publicPath: config.dev.assetsPublicPath, proxy: config.dev.proxyTable, quiet: true, watchOptions: poll: config.dev.poll, , plugins: [ //还记得之前说的生产环境和开发环境的变量在哪儿定义的吗?对,就是这里 new webpack.DefinePlugin( process.env: require(\'../config/dev.env\') ), //模块热替换的插件,修改模块不需要刷新页面 new webpack.HotModuleReplacementPlugin(), //当使用HotModuleReplacementPlugin时,这个插件会显示模块正确的相对路径 new webpack.NamedModulesPlugin(), //在编译出错时,使用NoEmitOnErrorsPlugin来跳过输出阶段,这样可以确保输出资源不会包含错误 new webpack.NoEmitOnErrorsPlugin(), new HtmlWebpackPlugin( filename: \'index.html\', template: \'index.html\', inject: true ), // 将static文件夹和里面的内容拷贝到开发模式下的路径,比如static下有个img文件夹,里面有张图片 // 我们可以这样访问:localhost:8080/static/img/logo.png new CopyWebpackPlugin([ from: path.resolve(__dirname, \'../static\'), to: config.dev.assetsSubDirectory, ignore: [\'.*\'] ]) ] ) //这里主要是做端口的检索以及npm run dev后对错误的处理,我们可以看这里使用了前面引入的 //\'friendly-errors-webpack-plugin\'插件 module.exports = new Promise((resolve, reject) => portfinder.basePort = process.env.PORT || config.dev.port portfinder.getPort((err, port) => if (err) reject(err) else // publish the new Port, necessary for e2e tests process.env.PORT = port // add port to devServer config devWebpackConfig.devServer.port = port // Add FriendlyErrorsPlugin devWebpackConfig.plugins.push(new FriendlyErrorsPlugin( compilationSuccessInfo: messages: [`Your application is running here: http://$devWebpackConfig.devServer.host:$port`], , onErrors: config.dev.notifyOnErrors ? utils.createNotifierCallback() : undefined )) resolve(devWebpackConfig) ) )
关于devServer有两点要说明一下:
- contentBase是来告诉服务器在哪里提供静态的内容,这里我们使用false的原因是使用了“copy-webpack-plugin”插件,不需要使用contentBase了;
- quiet开启后(true),除了初始启动信息之外的任何内容都不会被打印到控制台,即使是webpack 的错误或警告在控制台也不可见。不过我们用了\'friendly-errors-webpack-plugin\'插件,就可以设为true了。
6.webpack.prod.conf.js
经过前面这么多代码的分析,其实webpack.prod.conf.js的配置已经很简单了,大致跟webpack.dev.conf.js的配置方式差不多,就是多了几个plugins:
- UglifyJsPlugin是用来压缩JS代码
- optimize-css-assets-webpack-plugin是用来压缩css代码
- HashedModuleIdsPlugin会根据模块的相对路径生成一个四位数的hash作为模块id
- ModuleConcatenationPlugin可以预编译所有模块到一个包中,加快浏览器的运行速度
- CommonsChunkPlugin拆分公共模块,vue里拆分了vendor,manifest和app三个模块
- compression-webpack-plugin gzip压缩
- webpack-bundle-analyzer可以查看打包的具体情况,比如打了多少个包,每个包多大等
好了,plugins的介绍到此结束,接下来就是最后一个文件,也是npm run build编译时的入口文件——build.js了。
同样的,build.js文件其实也没什么可说的了,无非就是执行webpack.prod.conf.js文件,遇到错误时在命令行提示。需要注意的是,build.js里引入了“rimraf”的包,它的作用是每次编译时清空dist文件,避免多次编译时造成文件夹的重复和混乱。
四.结尾
到这里其实关于Vue-cli配置的分析基本结束了,相信了解webpack的朋友看起来一定非常简单,配置主要麻烦的地方在于低耦合导致经常需要来回翻文件才能看懂配置,如果大家结合着文章开头的脑图看可能会相对容易些。
一个坏消息是这个文章发布的时候webpack4.0已经上线了,Vue-cli新版也进入了Beta测试阶段,所以这篇文章大家看看就好,了解一下思路,马上配置又会更新的......
uni-app快速入门(转)(代码片段)
...pp支持通过 HBuilderX可视化界面、vue-cli命令行两种方式快速创建项目。通过HBuilderX可视化界面可视化的方式比较简单,HBuilderX内置相关环境,开箱即用,无需配置nodejs。开始之前,开发者需先下载安装如下工具:HBuilderX:官方... 查看详情
webpack入门——构建简易版vue-cli(代码片段)
用vue-cli1/2搭建一个vue项目时,可以看到有很多关于webpack配置的文件。我们不需要知道那些繁琐的配置文件有什么作用,只需在控制台输入npmrundev,项目自动启动,我们就可以愉快的写业务代码了。虽然vue-cli帮我们做好了一切,... 查看详情
springboot快速入门(代码片段)
01-内容Spring概述、快速入门SpringBoot配置SpringBoot整合02-SpringBoot概述SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻... 查看详情
swagger3.0快速入门(代码片段)
Swagger快速入门一。Swagger简介1.前后端分离2.Swagger引入springfox-swagger2SpringFox3.0.0发布swagger3.0与2.xx配置差异:具体使用教程如下1.导入依赖2.application.yml配置3.配置SwaggerAPI信息4.修改默认API文档显示页面配置Swagger自定义扫描接口... 查看详情
swagger3.0快速入门(代码片段)
Swagger快速入门一。Swagger简介1.前后端分离2.Swagger引入springfox-swagger2SpringFox3.0.0发布swagger3.0与2.xx配置差异:具体使用教程如下1.导入依赖2.application.yml配置3.配置SwaggerAPI信息4.修改默认API文档显示页面配置Swagger自定义扫描接口... 查看详情
vue-cli3.x安装配置步骤详细说明(代码片段)
一、vue-cli3.x简单介绍VueCLI是一个基于Vue.js进行快速开发的完整系统:是一个类似于 create-react-app 的可以用例命令行快速配置和生成一个vue项目。CLI:@vue/cli 全局安装的npm包,提供了终端里的vue命令(如:vuecreate... 查看详情
uwsgi快速入门(代码片段)
目录uwsgi快速入门一、概述1、简单介绍2、环境配置二、第一个WSGI应用1、运行2、添加并发三、结合Web服务器使用1、Flask2、Django3、Nginx配置uwsgi快速入门一、概述1、简单介绍WSGI(WebServerGatewayInterface),定义了web服务器(nginx、apa... 查看详情
ngnix配置文件快速入门(代码片段)
转自https://www.cnblogs.com/knowledgesea/p/5175711.html其实也没什么好说的,我想大部分人也不会在意nginx的实现原理啥的。服务器要部署的时候,把nginx装上去,配置文件配好,能跑起来,工作就已经达标了。如果要深究,可以转其他博... 查看详情
vue-cli入门——项目结构(代码片段)
前言在上一篇项目搭建文章中,我们已经下载安装了node环境以及vue-cli,并且已经成功构建了一个vue-cli项目,那么接下来,我们来梳理一下vue-cli项目的结构。总体框架一个vue-cli的项目结构如下,其中src文件夹是需要掌握的,所... 查看详情
saltsatck快速入门(代码片段)
SaltStack运行方式: Local Master/Minion SaltSSHSaltStack的三大功能: 远程执行 ##远程执行命令 配置管理 ##按照自己写好的文件,让minion端自动配置 云管理 ##调用云... 查看详情
nginx快速入门(代码片段)
nginx快速入门Nginx简介Nginx的特点Nginx作用Nginx安装windos安装启动Nginx检查nginx是否启动成功关闭nginxlinux下安装1、安装gcc2、PCREpcre-devel安装3、zlib安装4、OpenSSL安装下载linux版的安装包网址同上配置Nginx常用命令Nginx配置文件nginx.conf全... 查看详情
shell开发快速入门(代码片段)
shell开发快速入门文章目录shell开发快速入门初识bash什么是shellbashshell的主要功能shell的变量变量的设置与显示变量的规则变量的作用域变量的类型变量内容的变更bash的环境配置文件loginshell的配置文件读取流程bash的通配符bash的... 查看详情
mybaitsplus快速入门(代码片段)
MyBaitsPlus导入依赖主配置文件中的数据源相关配置UserMapper接口测试@Mapper注解和@MapperScan注解配置日志@TableId注解插入操作主键生成策略雪花算法(默认使用):主键自增其余的策略解释更新操作自动填充方式一:数据库级别(... 查看详情
vue入门教程第七篇(vue脚手架与发布)(代码片段)
...手架,是一个全自动生成vue项目的程序,使用它我们可以快速搭建一个基础的vue项目。vue-cli只是为了方便我们开发的工具,并非必须。npm安装脚手架程序: npminstall-gvue-cli 开始初始化项目(项目名称:project-name): v... 查看详情
git和github快速入门(代码片段)
Git入门简介Git是LinusTorvalds为了帮助管理Linux内核开发而开发的一个开放源码的分布式版本控制系统。工具准备工具Git下载地址:https://git-scm.comGit配置配置的内容主要是:用户名和邮箱gitconfig--global--adduser.name<用户名>gitconfig-g... 查看详情
nacos使用快速入门(代码片段)
Nacos使用快速入门引言Nacos做注册中心快速入门服务注册到nacos服务分级存储模型给user-service配置集群同集群优先的负载均衡权重配置环境隔离创建namespace给微服务配置namespaceNacos与Eureka的区别Nacos做配置中心快速入门在nacos中添加... 查看详情
tomcat和servlet快速入门教程!!!(代码片段)
Tomact和servlet快速入门教程tomcat的入门必备知识1.下载:2.安装:解压压缩包即可3.卸载:删除目录即可4.启动:5.关闭:6.部署:静态项目和动态项目将tomcat集成到IDEA中,并且创建JavaEE项目,部署项目Servletweb动态工程目录介绍热部署快速... 查看详情
springaop快速入门详解(代码片段)
文章目录AOP1.AOP简介1.1AOP简介和作用1.2AOP中的核心概念3.1AOP工作流程2.AOP入门案例2.1AOP入门案例思路分析2.2AOP入门案例实现(一)导入aop相关坐标(二)定义dao接口与实现类(三)定义通知类,制作通知方法(四)定义切入点表达式、配... 查看详情