您的位置:首页 > 产品设计 > UI/UE

vue的官方脚手架vue-cli到底做了什么?(vue-cli webpack配置详解)

2017-08-20 19:54 1486 查看
算起来有好几个月没记录学习的东西了,自己确实变懒了。。。

在这期间

1、看完了 underscore.js 的源码,分析了下公司项目中在vue-cli基础上扩展的一些webpack配置

2、开始了学习react,并利用 react 开始重写自己的新博客项目了,这次要好好做了,技术栈的话就 react react-router redux express就可以了,这次不打算使用 mongoose ,打算写原生的 mongodb 来好好学习语法,现阶段脚手架及周边工具基本配齐了,脚手架是参考 vue-cli 及 create-react-app 以及看着 webpack3 的文档全手动配置的,目录结构有点参考 dva 的教程的意思,正在做 微信授权 支付宝授权的登录

3、在做公司项目的过程中还看了 python 的语法,写了个很简单的上传代码后会自动build及覆盖测试服务器上相应打包文件的脚本,也是懒得自己手动搞这些,原理很简单,就是检测svn的版本号来 build 及 cp 到相应目录即可

4、遇到过一些异步队列的问题,感觉用 promise 解决不够优雅,又详细看了下 js runtime 下的 event loop的详解~发现co包可以解决,但根据源码来看,还是利用 ES6 生成器中的 yeild 和 promise 封装的,相对很优雅的解决方案。

以下为学习中推荐给大家的一些文档

webpack3 文档链接

python 学习链接-廖雪峰

dva 学习链接

event loop –micro task macro task 详解

相信 underscore 与 express、Node 等其他的一些相关链接应该不用我给了吧,大家都可以随便找到~

underscore.js 的话是强烈建议大家去看看的,尤其是它的 中间函数 与 一些断言 来实现函数式编程的思路,Function 那章的 节流函数(.throttle) 及 延时函数(.delay) 建议一定要看,比我之前写的严谨太多,实现方式是真的精巧,以后有空可能会拉出来分析一下。

js 运行环境下的任务队列 event loop 也是绝对需要补的基础, 虽然 co 包和 ES6 的 Generator 都解决了这个问题。

讲解的为vue-cli webpack模板 2017年8月份之前的版本,初始化后如果有不一致,就需要重点注意这个版本问题了,后续修改后会有些许代码不一样

话不多说,进入本次文章正题。

vue-cli

build

build.js

check-versions.js

dev-client.js

dev-server.js

utils.js

vue-loader.conf.js

webpack.base.conf.js

webpack.dev.conf.js

webpack.prod.conf.js

config

dev.env.js

index.js

prod.env.js

src

static

.babelrc

package.json



以上是vue-cli初始化项目后的纯净项目目录,大家应该都是这样,注意不是简化版脚手架
webpack-simply
,这个就没啥好讲了= =。。。就一个webpack基本配置,没有热加载和 eslint 的。

一、首先我们直接看 package.json 中的脚本命令,从执行处开始倒推vue-cli来讲解主题

// package.json



这里只截取部分需要讲解的地方,大家可以看到初始化项目 install 的vue版本是@2.3.3,截取这个部分是为了让大家看到我是在说新的,不是旧的= =

首先我们看到
npm run start
npm run dev
是一样的,都是跑 build/dev-server.js 所以归于一个来讲,所以这里我们其实只需要讲解2个命令

1、npm run dev

2、npm run build

二、npm run dev

1、首先大家打开后,进去看到第一行是
require('./check-versions')()
即加载当前目录下的 check-versions.js 并直接执行此函数,见名思意,其实这里的用处就是检测我们当前的 node 进程中运行的 node 与 npm 的版本,如果不符合会在 cli 下返回相应错误,为此我决定带大家看看里面有哪些包以及是如何做到的,请看下一点;

2、这里我以图片的形式来代替代码块,之后我会在图片下插入注释及所有包的文档地址供大家参考,不然看起来就全是字了,我自己都看不下去

// check-version.js 第一块解释



chalk 包 –正如上传者对包名的简介

“Terminal string styling done right” 终端字符串样式修正,说白了就是在 cli 界面可以显示花里花俏颜色的字

semver 包 –用于取版本后的版本对比

后续有个api
satisfies(version, range): Return true if the version satisfies the range


可以字节取版本号的字符串与一个范围作对比, eg: semver.satisfies(1.2.3, >=2.0.0) 这里就返回 false 了,因为不满足

shelljs 包 –可以直接以代码的形式在命令行跑shell,这个不多说了,文档里写的太清楚

exec 函数,开辟一个子进程,执行node子进程 execSync 的方法,它会将传入的命令跑完后,在子进程被结束时返回其结果,这里返回后还转为为了字符串的形式并前后去空格, 转化为字符串时为了方便后续使用我前面说的
semver.satisfies(version, range)


随后建立 versionRequirements 数组里面有2个对象,一个是用来验证 node 的版本一个是 npm
semver.clean
获取过滤后的版本号,过滤只有 X.X.X 的形式
shell.which('npm')
获取npm在当前环境下的路径,存在则会返回路径地址,好,下面就可以解释check-versions.js 的下一段exports的函数了



这里大家看到就很简单了,对
versionRequiremenets
数组进行循环,并去检测当前版本是否符合 package.json 内定义的版本,若不符合则黄色字体输出一些info,红色字体 cli 界面输出当前版本,绿色字体输出需要的版本,最后退出这段检测进程并返回 1 作为状态码。

好,check-versions.js 分析结束,下面我们回到 dev-server.js,我们还是以图片加注释来讲解

3、dev-server.js 代码及node包的分析



首先我们看到先将 config/index.js export的对象复制给了 config 变量(若不指定目录下哪一文件会默认加载该目录下的index文件), 而 config/index.js 的分析解读我们留到后面讲,这里用到什么就讲什么。

紧接着赋值了当前进程的环境,加载的是 config.dev.env => require(‘./dev.env’).NODE_ENV => ‘development’, 这样写大家应该看的懂吧, 因为 dev.env.js export的对象其实就是{ NODE_ENV: ‘development’ }

接下来引入各类包

opn 包 –就是一个可以打开各类文件或网站的工具包

用法非常简单,文档也简洁 eg: opn(‘http://XXX.XXXX.XX‘, {app: [‘google chrome’]}) 或者 opn(‘file.png’).then(), 支持promise结构

path 包 –node自带的包,可用于路径的合并,不用再拼字符串啦!

express –这个不多说了吧,node 的一个 web 框架,自行学习,通过express封装简化了node的操作 eg: http.Server().listen() => new express().listen(), 其他相关操作请各位自行看文档

http-proxy-middleware 中间件 –用于请求的代理设置

eg:


(可用于解决本地开发的跨域问题,在请求时服务器会像代理服务器请求后在本地返回,相当于一个中间层的处理)

这里首先定义监听的端口,先像node进程中获取,查看是否服务端监听的端口,若无则读取 /build/index.js 中 dev 下的 port 的值,默认为8080

最后赋值代理配置 proxyTable, 实例化 express , 调用 dev 配置进行webpack的编译

紧接着进行的就是引入 webpack 的 dev-server 中间件了,这是 node 中引入的用法,webpack3之后我们使用 dev-server 在 webpack 中可以不用在 package.json 后加命令后缀了,在基本配置中加上即可,
publicPath
指稍后需要保存到哪个路径

devMiddlware 中
quiet
代表不要输出任何东西在控制台, 然后引入在引入 dev-server 的基础上引入 热重载中间件
webpack-hot-middleware
log
quiet
一样,
heartbeat
为更新客户端(在这里我们就是网页了)的时间间隔,与在没有 socket 之前的聊天室同理,我说心跳连接大家应该都懂了,也就说这里就是 2s 会检测一次客户端是否有变化,若有变化后续我们会有一个回调来执行刷新,这里还需要重点说的是

1、
webpack-hot-middleware
依赖于
webpack-dev-middleware
, 两者都是在 webpack 编译后进行绑定;

2、使用 dev-server 后将不会产生文件流输出到磁盘中,而是存在当前服务器内存中,所以
npm run dev
你是看不到有文件生成的,我们在基本配置中的 publicPath 目录一定要填 ‘/’ 根目录下的XX,不能使用相对路径,因为它将保存在服务器下,本地相对路径无法读取,否则会出现 cli 在跑 dev-server 但是页面没更新也没办法重载的情况,因为输出流地址错误了;

相关文档

webpack-dev-middleware

webpack-hot-middleware

webpack3中的dev-server配置



我们继续开始第二部分的分析

上来直接执行
plugin
的 compilation 参数,如果我没翻译错的话, compilation 参数,顾名思义这里是整个编译过程中最主要的生命周期,所有的模块都会被重载,所以在这里我们需要进行的当然就是热重载后的刷新页面或替换模块了。然后再执行我们事先安装的 webpack 插件
html-webpack-plugin
绑定到 compilation 中的事件 ‘html-webpack-plugin-after-emit’ 来扩展我们对 HTML 的操作,在回调内我们对热重载中间件执行其事件流后添加的 publish 方法向服务器进程发出重载通知,里面第一个参数是 一个对象,我们只需要改变后续执行的 action 为 ‘reload’ 就行,publish的参数文档中没提,但我们在源码中可以很容易的找到它其实就是个事件流的通知发布,而 action 为 ‘reload’ 这个是我们在 /build/dev-client.js 中自定义的事件行为,当等于 ‘reload’ 时,执行的是
window.location.reload()
, 当然这里写明了客户端的配置中是添加了
reload = true
的,下面是 build 目录下的 dev-client.js



下面是 webpack-hot-middleware/middleware.js 中的部分源码





接下来的遍历 proxyTable 的配置就简单了, 前面已经提到 proxyTable 是从 config/index.js 中的 dev 下调过来的,我们看到里面的配置其实 key 值就对应了
http-proxy-middleware 这个中间件的 app.user(context, options)
context, value 就对应了
options
, 然后通过遍历,依次添加代理。

下面的几个 express 的
use
大家看注释应该都懂啦,

1、使用 connect-history-api-fallback 这个包来处理单页应用中的历史记录,避免单页情况下,访问二级子组件时去 get 这个地址不存在而导致 404 ,具体大家自己去看文档吧~还可以正则匹配路由 redirect 回指定页面;

2、执行 dev-server ,让 webpack 命令执行;

3、执行热重载插件;

下来继续,监听
staticPath
=>
/static
访问它相当于访问 ./static 目录下的静态资源,
express.static(root, [options])
是 express 唯一内置的中间件,用于挂载访问静态资源,最后声明监听的 hostname + port, 然后创建一个 promise, 抽出 resolve 方法,第二段结束~

接下来是 dev-server.js 的最后一部分了



调用 dev-server 中间件的 api
waitUntilValid(callback)
用于
webpack-dev-middleware
绑定生效或再次生效后的回调,在里面我们看到的是只要 autoOpenBrowse 存在且 当前 node 进程的环境不是测试环境就利用 opn 包打开我们监听的 uri , 最后主函数都跑完后调用 promise 的 resolve, 这个函数实际上是自定义的,因为大家可以看到最后一段代码 exports 的对象

最后 listen 开启服务监听 port 端口, 然后 export 一个后续可供操作的对象, ready 是 dev-server 的配置服务都完全启动且打开页面后的一个回调, close 方法则是关闭服务,抽出这个对象以防后续可能会在代码中手动操作,从这里开始由 dev-server 配置的本地服务就跑起来并且页面打开为大家看到的样子了,ok~ npm run dev 终于完结!!!

三、npm run build

讲这段之前大家一定要记得看 /build/webpack.base.conf.js 喔,这是webpack的基本配置,是一些加载器的配置以及入口及出口文件的配置,注意看出口文件的公共路径
publicPath
中的判断,你就知道 /config/index.js 里的
assetsPublicPath
是干啥的了。



在里面还配置了对 vue-loader 的一些配置,例如是否进行 sourceMap 压缩代码, sourceMap 其实就是进行代码压缩以及文件合并,减少加载页面时的 http 请求, 而生成的 map 后缀文件里面记录了调试压缩后的代码后对应找到源文件的行数与列数位置,方便调试。

阮老师的 SourceMap 详解

好的,上正文,
npm run build
跑的就是
node build/build.js
, 我们和
dev-server.js
一样去分析 /build/build.js 即可。



上面讲过的一些包和代码分析我就不再讲了,例如 ‘./check-versions’、 chalk 包这些

ora 包 –这真的是个有趣的包,它用于在 cli 界面下显示五颜六色的 loading 动画,原理就是按照时间间隔遍历字符串哈哈,如果想要更多有趣的loading动画,去看看这个包吧 cli-spinners 看看它的
spinners.json
我惊了(@ο@)

rimraf 包 –让你可以直接用linux的 rm -rf 命令

/build/webpack.prod.conf
这里的合并了
/build/webpack.base.conf.js
内的配置,简单来说就是对象的合并 , 在
/build/dev-server.js
内也有, 我放到后面统一单独讲

后面就是启动 ora 这个包啦,这个非常简单,看文档吧,例子我都不举啦,直接下一段了。



执行 rm 命令删除指定相对路径下 build 出来的目录下的所有文件,成功后的回调内,
spinner.stop()
停止 loading 动画的循环,执行执行 webpack 的 node API 开始打包并向当前 node 的 webpack 打包进程输入流 写入 stats 的属性配置,相应配置的含义我会在下面贴出来文档,大家自行查询。

Node API process.stdout.write()

webpack stats配置列表

最后输出一些说明日志,build.js 就是这么简单,完结~接下来就是一些 webpack 的配置说明了~

四、webpck.base.conf.js 内的一些配置说明

首先先第一段吧



utils.js 与 vue-loader.conf.js 单独讲不好讲,我们再下面代码中穿插着讲, 因为 utils.js 中输出了多个函数来方便调用。

entry
: webpack 打包的入口文件,type: String || Array || Object

output
: webpack 出口配置,打包后的目录及模块加载的公共路径及文件名等配置,具体查看webpack文档即可,里面有很详细的讲解,这里不做细讲

resolve
: webpack 的模块解析器,
extensions
可解析的扩展名, 值要为数组,里面的扩展名不用说了吧?
alias
别名, 即解析到对应的 key 字符串时会自动对应解析为 key 对应的 value 值,用过 linux 别名的应该都懂~下一段



模块加载器规则,这里我说几个重点及我还记得的遇到过的坑

1、webpack2后
loader
加载器的值不能简写了,如 babel-loader, 我们以前会不写 loader 而直接写babel,会自动补全为 ‘babel-loader’,但是现在不会了,3更要避免这种写法;

2、webpack1中我们使用Eslint时会时规则会写在
preLoaders
里, 注意现在不可以了,而是由
enforce: 'pre'
代替为加载器预处理;

3、当你想在 react 项目中, 在需要用到组件内的状态管理
this.setState
时,我们会选择利用
class XXX extends React.Component
的方式新建类来继承为 React 组件类,为了能在里面直接使用箭头函数而不用麻烦的使用Fucntion.prototype.bind(context, [args]), 我们会安装2个 babel 插件
babel-preset-stage-0
babel-plugin-transform-class-properties
, 装完后注意要在 js 文件的加载器那将原本写为
exclude: '/node_modules/'
改为
include: '你自己需要使用babel转换的目录'
, 否则会报错 $export 不是一个函数,就像这里
vue-cli
的 eslint 需要处理的目录一样, 我记得没错的话好像是 babel 插件的锅,这里我还没有深究其原因= =。。。如果大家知道,希望能留言告诉我,蟹蟹;

4、我们来讲讲这里的
vueLoaderConfig
即 vue-loader.conf.js 的配置是干啥的。

在解释 vue-loader.conf.js 之前我们必须先看
build/utils.js
里的
cssLoaders
这个方法里面干了什么



首先在 utils.js 中我们看到引入了
extract-text-webpack-plugin
这个 webpack 插件, 使用它的 extract api 可以创建额外的加载器在已存在的加载器下,所以这里用于 cssLoaders 就是为了使 .vue 文件的加载器可以使用多个 css 预处理器的加载器, 而在 /build/vue-loader.conf.js 中我们看到 loaders 下的 extract 开启的条件是当前 node 进程的环境是生产环境时便满足 cssLoaders 使用 ExtractTextPlugin extract,因为生产环境打包需要释出 css 文件,所以最终返回的对象也是为此处的 loaders push 各类 css 预处理器的 loader,所以整体上我们大概已经知道这段函数其实就是在生成 ExtractTextPlugin对象或loader字符串,在非生产环境的条件下,我们不需要 ExtractTextPlugin 来生成真正的 css 文件,所以存在 extract 这个判断,所以在 webpack.base.conf.js 中的 vueLoaderConfig 在非生产环境下就是一个 loaders 对象用于 .vue 文件中可以处理各类 css 预处理器, 在 generateLoaders 内就是对每种 css 预处理器需要的 loader(Array) 进行了一个拼接。

extract-text-webpack-plugin extra 说明

5、 vueLoaderConfig 说完了,我们回过来继续分析 utils.assetsPath 这个用于生成静态资源路径的函数,这个函数就非常简单了,就是简单讲生成路径组装起来,传入的参数就是生成对应的文件目录下与名字拼接起来的字符串,这里需要注意的是 .ext 代表一个通用的文件扩展名,当我们不确定文件为什么扩展名时使用,在这里 [ext] 就是说会自动去匹配扩展名。

ok,base的配置讲完啦

五、webpack.dev.conf.js 内的一些配置说明

由前面分析我们已经知道, webpack.dev.conf.js 是在开启 dev-server.js 时引入的 webpack 开发环境的配置,现在我们就来看看开发环境的配置到底与 base 的基本配置有什么不同。



照常,先解释一些引入包的作用

html-webpack-plugin —前面已经大概给大家了解过了,这是一个与webpack绑定起来作为单页首页的唯一 html 首页模板,在其中我们可以声明各类渲染模板作为首页也是可以的,如 ejs 、jade 都是可行的,一般作为 vue 或 react 的根节点的绑定,建议利用渲染模板来替代首页,可以更加灵活的加入各类参数的配置,如日常开发环境下的测试账号信息等配置,具体用法大家自行参照文档,相对简单。

friendly-errors-webpack-plugin —可有可无的插件,主要为了开发体验,是用于自定义 cli 界面报错的友好型信息,开发者可以自定义信息,或使用插件默认的,主要用于快速定位报错位置。

首先遍历基本配置中的入口文件,令其在 dev-client.js 所自定义的热重载事件流下作为入口给 webpack 打包,令后续 webpack 的 compiler 事件流中有 reload 这个动作,这就解释了前者 dev-server.js 中的webpack 为何会拥有 reload 这个 action了

其次则是利用 webpack 的 merge 合并 base 中的配置, 这个 merge 类似于 jq 的 $.extend 或 underscore 中的 _.extend , 用于合并对象,接下来让我们看看在 base 配置的基础上还加了什么;

1、在 rules 中增加了多个 css 预处理器的加载器



我们看到 styleLoaders 方法最后返回的实质上就是 cssLoaders 中返回的各类 css 预处理器的 loader, 前面我们在 base 中使用 cssLoaders 作为 .vue 这个模板文件 loader 下的子加载器,而这次我们则利用 cssLoaders 作为 webpack 的外层配置,即不在依赖于 .vue 文件了,使其在任何情况下都可以直接使用,因为在此处合并了各类 css 预处理器的加载器规则。

2、利用 devtool 添加 sourceMap 配置

#cheap-module-eval-source-map
这种生成 sourceMap 的命令被描述为: 不包含列信息,同时 loader 的 sourcemap 也被简化为只包含对应行的。最终的 sourcemap 只有一份,它是 webpack 对 loader 生成的 sourcemap 进行简化,然后再次生成的。(其实这么多种 sourceMap 的方式,我也并不理解。。。),这里姑且就当做是开启了 sourceMap 就好~

3、各类新添加的 webpack plugins

definePlugin
: 打包时会将你在其中自定义的 key 作为全局变量,而 value 就是它的值了, 在这里我们看到是定义了 process.env 这个全局变量为 config.dev.env 即 config/dev.env.js export的对象,值得注意的是里面有个键名为 NODE_ENV 为 ‘development’, 这就让我们后面每次用到这些配置的地方不用去手动引入更改了, 而一直是注入在项目中的,减少了人为手动操作而带来的忘记更改的错误,详细用法大家可以去官网看,或看这篇博客 webpack.DefinePlugin({}) 的用法

HotModuleReplacementPlugin
: 不多说了,热替换的插件,使你可以在不用重绘整个页面的情况只是单独重新加载部分模块,这样改动小时就不会老是要刷新整个页面了。

NoEmitOnErrorsPlugin
: 可以跳过存在的错误而继续webpack的编译,即使部分模块报错也没事。

HtmlWebpackPlugin
: 上面已经提过了,这里就是添加了相关配置而已,令
index.html
成为模板且文件名也为它,
inject
指注入所有相关配置到 html 中的 body 中, true 为默认 body,可定义为 ‘head’ 注入到 head 中。

FriendlyErrorsPlugin
: 前面已经提过了。

六、webpack.prod.conf.js 内的一些配置说明

老套路,先说包,再说配置



copy-webpack-plugin —webpack用于复制文件以及文件夹的插件到指定目录下;

extract-text-webpack-plugin —用于分离 css 文件,前面已经分析过;

optimize-css-assets-webpack-plugin —用于优化 css 的代码及压缩,开发环境下没必要。

1、新增的 rules 与 detool 在 webpack.dev.conf.js 中已经解释过,这里只是参数变为了生产环境的 build 配置而已, 出口文件的配置也是根据 assetPath 函数判断为 build 下的配置而已,变更的只是生成的 js 文件独立放到了一个 js 的文件夹下而已,这些没什么讲的了;

2、新增的 plugins 需要讲讲, 与 dev 环境下一样的就不讲了

UglifyJsPlugin
: 用于压缩 js 文件 [UglifyjsWebpackPlugin];(https://webpack.js.org/plugins/uglifyjs-webpack-plugin/#components/sidebar/sidebar.jsx)

ExtractTextPlugin
: 里面的 filename 配置值压缩出的 css 文件的路径;

OptimizeCSSPlugin
: 里面
cssProcessorOptions
内的
{safe: true}
, 记得加上,这是保证 build 后由于插件为你 css 代码优化过程中令某些 css 样式不被重置,说白了就是优化过程中,不让他重置代码,比如
z-index
这种层级关系被优化就蛋疼了;

HtmlWebpackPlugin
: 上面说过好多次了,这里提一下新增的几个配置的意思↓

removeComments: 删除 HTML 中缩写内容的空格以及换行,比如一般来说我们,最小化配置文档在这 [html-webpack-plugin minify配置列表](https://github.com/kangax/html-minifier#options-quick-reference)

collapseWhitespace: 去掉标签内的无用空格,并会合并相邻的相同标签

removeAttributeQuotes: 删掉标签内一些无用及未被使用属性,如 id=” class=” 会被直接删掉,而本来在html attribute 中 id class 这类就是被解释为字符串,所以也会删掉 id=”id” 这种情况下的双引号

CommonsChunkPlugin
: 将 js 分割为独立的文件;

CopyWebpackPlugin
: 这里复制了挂载在 static 文件夹下的静态资源,确保 static 内的图片或其他文件不被压缩打包。

3、最后的 2 个判断而加载的 2 个插件

compression-webpack-plugin
: 在 build 配置中开启 gzip 的情况下, 引入 webpack 压缩插件 assest: 被压缩的目标; algorithm: 压缩算法; test: 符合该正则的配置才被压缩,默认是所有的; threshold: 只压缩被这个配置值更大的文件; minRatio: 只有比这个压缩比例压缩出来更优的文件才进行压缩,默认是0.8,即压缩到80%

webpack-bundle-analyzer
: 在开启模块绑定分析报告的情况下,引入这个插件,好像是会生成一个可视化界面供你查看 webpack 绑定模块分布情况(没用过这个= =) webpack-bundle-analyzer

七、总结

写着写着就写了这么多了,整个论文一样。。。 阅读体验应该会比较差,我已经尽自己可能写的够详细了,其中若存在比较含糊与错误的地方,欢迎大家指出,我会及时纠正~蟹蟹,我也不想这么多的,真的好累啊~~(>_<)~~。。。怎么就这么多了~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  vue webpack vue-cli Node