前言
webpack2和vue2已经不是新鲜东西了,满大街的文章在讲解webpack和vue,但是很多内容写的不是很详细,对于很多个性化配置还是需要自己过一遍文档。Vue官方提供了多个vue-templates,基于vue-cli用官方的webpack模板居多,不过对于很多人来说,官方的webpack模板的配置还是过于复杂,对于我们了解细节实现不是很好,所以想自己从零开始搭建一个模板工程,也顺便重新认识一下webpack和vue工程化的细节。
webpack 核心概念
安装
在开始前,先要确认你已经安装Node.js的最新版本。使用 Node.js 最新的 LTS 版本,是理想的起步。使用旧版本,你可能遇到各种问题,因为它们可能缺少 webpack 功能或缺少相关 package 包。
本地局部安装:
# 安装 latest release
npm install --save-dev webpack
# 简写模式
npm install -D webpack
# 安装特定版本
npm install --save-dev webpack@<version>
全局安装:
npm install -g webpack
注意:不推荐全局安装 webpack。这会锁定 webpack 到指定版本,并且在使用不同的 webpack 版本的项目中可能会导致构建失败。但是全局安装可以在命令行调用 webpack 命令。
【补充】npm install 安装模块参数说明:
-g, --global 全局安装(global)
-S, --save 安装包信息将加入到dependencies(生产阶段的依赖)
-D, --save-dev 安装包信息将加入到devDependencies(开发阶段的依赖),所以开发阶段一般使用它
-O, --save-optional 安装包信息将加入到optionalDependencies(可选阶段的依赖)
-E, --save-exact 精确安装指定模块版本
npm 相关的更多命令参考这篇文章:npm 常用命令详解
然后在根目录下创建一个 webpack.config.js
文件后,你可以通过配置定义webpack的相关操作。
入口(Entry)
单个入口(简写)语法:
用法:entry: string|Array<string>
webpack.config.js:
module.exports = {
entry: './src/main.js'
};
对象语法:
用法:entry: {[entryChunkName: string]: string|Array<string>}
webpack.config.js:
module.exports = {
entry: {
app: './src/main.js',
vendor: ['vue']
}
};
这里我们将vue作为库vendor打包,业务逻辑代码作为app打包,实现了多个入口,同时也可以将多个页面分开打包。
多页面应用程序通常使用对象语法构建。对象语法是“可扩展的 webpack 配置”,可重用并且可以与其他配置组合使用。这是一种流行的技术,用于将关注点(concern)从环境(environment)、构建目标(build target)、运行时(runtime)中分离。然后使用专门的工具(如webpack-merge)将它们合并。
注:vue-cli 生成的模板中build文件夹下有四个配置文件:
webpack.base.conf.js:基本配置
webpack.dev.conf.js:开发阶段配置
webpack.prod.conf.js:准生产阶段配置
webpack.test.conf.js:测试配置
后三个文件通过webpack-merge插件合并了基本配置,将不同环境下的配置拆分多个文件,这样更加方便管理。
出口(Output)
在 webpack 中配置output 属性的最低要求是,将它的值设置为一个对象,包括以下两点:
output.filename:编译文件的文件名;
output.path对应一个绝对路径,此路径是你希望一次性打包的目录。
单个入口:
const path = require('path');
module.exports = {
entry: './src/app.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build') //__dirname + '/build'
}
}
多个入口:
如果你的配置创建了多个 "chunk"(例如使用多个入口起点或使用类似CommonsChunkPlugin 的插件),你应该使用以下的替换方式来确保每个文件名都不重复。
[name] 被 chunk 的 name 替换。
[hash] 被 compilation 生命周期的 hash 替换。
[chunkhash] 被 chunk 的 hash 替换。
const path = require('path');
module.exports = {
entry: {
app: './src/main.js',
vendor: ['vue']
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'build')
}
}
// 写入到硬盘:./build/app.js, ./build/vendor.js
加载器(Loaders)
在你的应用程序中,有三种方式使用 loader:
这里我们主要说明一下使用webpack.config.js配置,使用loader需要在module的rules下配置相应的规则,以css-loader的webpack.config.js为例说明:
module.exports = {
module: {
rules: [
{test: /\.css$/, use: 'css-loader'}
]
}
};
这三种配置方式等效:
{test: /\.css$/, use: 'css-loader'}
{test: /\.css$/, loader: 'css-loader',options: { modules: true }}
{test: /\.css$/, use: {
loader: 'css-loader',
options: {
modules: true
}
}}
注:loader/query可以和options可以在同一级使用,但是不要使用use和options在同一级使用。
CSS样式分离
为了用 webpack 对 CSS 文件进行打包,你可以像其它模块一样将 CSS 引入到你的 JavaScript 代码中,同时用css-loader(像 JS 模块一样输出 CSS),也可以选择使用ExtractTextWebpackPlugin(将打好包的 CSS 提出出来并输出成 CSS 文件)。
引入 CSS:
import 'bootstrap/dist/css/bootstrap.css';
安装css-loader和style-loader:
npm install --save-dev css-loader style-loader
在 webpack.config.js 中配置如下:
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}]
}
}
资源路径处理
因为.png等图片文件不是一个 JavaScript 文件,你需要配置 Webpack 使用file-loader或者url-loader去处理它们。使用它们的好处:
file-loader 可以指定要复制和放置资源文件的位置,以及如何使用版本哈希命名以获得更好的缓存。此外,这意味着 你可以就近管理你的图片文件,可以使用相对路径而不用担心布署时URL问题。使用正确的配置,Webpack 将会在打包输出中自动重写文件路径为正确的URL。
url-loader 允许你有条件将文件转换为内联的 base-64 URL(当文件小于给定的阈值),这会减少小文件的 HTTP 请求。如果文件大于该阈值,会自动的交给 file-loader 处理。
安装 file-loader 和 url-loader:
npm install --save-dev file-loader url-loader
配置说明:
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'img/[name]_[hash:7].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]'
}
}
插件(Plugins)
想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,你需要使用 new 创建实例来调用它。
生产环境构建
对于Vue生产环境构建过程中压缩应用代码和使用Vue.js 指南 - 删除警告去除 Vue.js 中的警告,这里我们参考vue-loader文档中的配置说明:
if (process.env.NODE_ENV === 'production') {
// http://vue-loader.vuejs.org/zh-cn/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: false,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
显然我们不想在开发过程中使用这些配置,所以这里我们需要使用环境变量动态构建,我们也可以使用两个分开的 Webpack 配置文件,一个用于开发环境,一个用于生产环境,类似于vue-cli中使用 webpack-merge 合并配置的方式。
可以使用 Node.js 模块的标准方式:在运行 webpack 时设置环境变量,并且使用 Node.js 的process.env来引用变量。NODE_ENV变量通常被视为事实标准(查看这里)。使用cross-env包来跨平台设置(cross-platform-set)环境变量。
安装cross-env:
npm install --save-dev cross-env
设置package.json中的scripts字段:
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
}
这里我们使用了cross-env插件,cross-env使得你可以使用单个命令,而无需担心为平台正确设置或使用环境变量。
模块热替换
这里我们使用webpack-dev-server插件,webpack-dev-server 为你提供了一个服务器和实时重载(live reloading)功能。webpack-dev-server是一个小型的node.js Express服务器,它使用webpack-dev-middleware中间件来为通过webpack打包生成的资源文件提供Web服务。它还有一个通过Socket.IO连接着webpack-dev-server服务器的小型运行时程序。webpack-dev-server发送关于编译状态的消息到客户端,客户端根据消息作出响应。
安装 webpack-dev-server:
npm install --save-dev webpack-dev-server
安装完成之后,你应该可以使用 webpack-dev-server 了,方式如下:
webpack-dev-server --open
上述命令应该自动在浏览器中打开 http://localhost:8080。
webpack.config.js配置:
module.exports = {
...
devServer: {
historyApiFallback: true, // 任意的 404 响应都替代为 index.html
hot: true, // 启用 webpack 的模块热替换特性
inline: true // 启用内联模式
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
...
}
更多的配置说明可以看文档:DevServer
动态生成 html 文件
该插件将为你生成一个HTML5文件,其中包括使用script标签的body中的所有webpack包,也就是我们不需要手动通过script去引入打包生成的js,特别是如果我们生成的文件名是动态变化的,使用这个插件就可以轻松的解决,只需添加插件到您的webpack配置如下:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
})
]
...
}
提取 CSS 文件
extract-text-webpack-plugin
是一个 可以将 *.vue
文件内的 <style> 提取,以及JavaScript 中导入的 CSS 提取为单个 CSS 文件。配置文档具体见这里:extract-text-webpack-plugin。
安装:
npm install --save-dev extract-text-webpack-plugin
配置:
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new ExtractTextPlugin("styles.css"),
]
}
同时支持我们可以配置生成多个css文件,这样我们可以将业务逻辑代码和引用的样式组件库分离。
const ExtractTextPlugin = require('extract-text-webpack-plugin');
// Create multiple instances
const extractCSS = new ExtractTextPlugin('stylesheets/[name]-one.css');
const extractLESS = new ExtractTextPlugin('stylesheets/[name]-two.css');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: extractCSS.extract([ 'css-loader', 'postcss-loader' ])
},
{
test: /\.less$/i,
use: extractLESS.extract([ 'css-loader', 'less-loader' ])
},
]
},
plugins: [
extractCSS,
extractLESS
]
};
clean-webpack-plugin
在编译前,删除之前编译结果目录或文件:
npm install --save-dev clean-webpack-plugin
配置:
plugins: [
new CleanWebpackPlugin(['dist'])
]
这样当我们在构建的时候可以自动删除之前编译的代码。
解析(Resolve)
这些选项能设置模块如何被解析。webpack 提供合理的默认值,但是还是可能会修改一些解析的细节。
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': path.join(__dirname, 'src')
},
extensions: ['.js', '.json', '.vue', '.css']
}
我们使用最多的就是别名(alias)和自动解析确定的扩展(extensions),例如上面的@可以代替项目中src的路径,例如:
import tab from '@/components/tab.vue'
我们引用src/components目录下的tab.vue组件,不需要通过../
之类的计算文件相对路径。这里的extensions可以让我们在引入模块时不带扩展:
import tab from '@/components/tab'
至此我们已经学习了我们项目devDependencies依赖中常用的模块:
webpack
css-loader / style-loader
file-loader / url-loader
cross-env
webpack-dev-server
html-webpack-plugin
extract-text-webpack-plugin
clean-webpack-plugin
这里我们只说明了css、图片、html模板资源webpack相关的加载器和插件,对于js相关的内容丝毫没有提到,显然这是不合乎情理的。之所以要把js单独拿出来是因为js相关的内容很重要,独立出来详细去归纳一下更合适。
webpack 中如何使用 es6 ~ es8?
作为一个前端,相信 es6 几乎是无人不知,很多人也一定知道可以使用Babel做语法转换,但是对于Babel有哪一些版本,每个版本支持的es6语法有哪一些应该不是所有人都清楚的,这就是这部分内容要写的意义。毕竟如果我们的插件只用到了es6中的没一些新特性,为此将整个包引入就有点不太合适,另外为了更好的用上新特性,我们至少要明白有哪一些新特性吧。
ECMAScript 标准建立的过程
ECMAScript 和 JavaScript 的关系在此不再赘述,建议阅读一下阮一峰老师的《ECMAScript 6简介》,我们需要了解的是从ECMAScript 2016开始,ECMAScript将进入每年发布一次新标准的阶段。制定ECMAScript 标准的组织是ECMAScript TC39,TC39(ECMA技术委员为39)是推动JavaScript发展的委员会。 它的成员是都是企业(主要是浏览器厂商)。TC39会定期的开会, 会议的主要成员时是成员公司的代表,以及受邀请的专家。
一种新的语法从提案到变成正式标准,需要经历五个阶段。每个阶段的变动都需要由 TC39 委员会批准。
Stage 0 - Strawman(展示阶段)
Stage 1 - Proposal(征求意见阶段)
Stage 2 - Draft(草案阶段)
Stage 3 - Candidate(候选人阶段)
Stage 4 - Finished(定案阶段)
建议看一下alinode 团队的图说ECMAScript新标准(一)就可以大致了解整个过程。
安装 Babel
Babel 现在的官网提供了一个可以根据你的工具提示下载合适的包,具体见这里:Using Babel。
如果你想要在命令行使用Babel,你可以安装babel-cli,但是全局的安装babel-cli不是一个好的选择,因为这样限定了你Babel的版本;如果你需要在一个Node项目中使用Babel,你可以使用babel-core。
我们这里自然选择webpack构建我们的工程,下载方案如下:
npm install --save-dev babel-loader babel-core
然后我们需要在项目根目录下建立.babelrc
文件:
{
"presets": [],
"plugins": []
}
注:在window下无法通过 右键=>新建 命令来创建以点开头的文件和文件夹,我们可以通过下面的命令生成.babelrc
文件:
type NUL > .babelrc
Linux和Mac下可以通过touch命令生成:
touch .babelrc
Babel 预设(presets)
Babel是一个编译器。 在高层次上,它有3个阶段,它运行代码:解析,转换和生成(像许多其他编译器)。默认情况下,Babel 6并没有携带任何转换器,因此如果对你的代码使用Babel的话,它将会原文输出你的代码,不会有任何的改变。因此你需要根据你需要完成的任务来单独安装相应的插件。
你可以通过安装插件(plugins)或预设(presets,也就是一组插件)来指示 Babel 去做什么事情。Babel 提供了多个版本的官方预设:
babel-preset-env
babel-preset-env可以根据你配置的选项,自动添加一些其他的转换器,来满足你当前的装换需求。.babelrc文件新增了options选项:
{
"presets": ["env", options]
}
具体的配置内容:
targets.node 支持到哪个版本的 node
targets.browsers 支持到哪个版本的浏览器
loose 启动宽松模式,配合 webpack 的 loader 使用
modules 使用何种模块加载机制
debug 开启调试模式
include 包含哪些文件
exclude 排除哪些文件
useBuiltIns 是否对 babel-polyfill 进行分解,只引入所需的部分
babel-preset-es2015
es2015(ES6)相关方法转译使用的插件,具体见文档。
check-es2015-constants // 检验const常量是否被重新赋值
transform-es2015-arrow-functions // 编译箭头函数
transform-es2015-block-scoped-functions // 函数声明在作用域内
transform-es2015-block-scoping // 编译const和let
transform-es2015-classes // 编译class
transform-es2015-computed-properties // 编译计算对象属性
transform-es2015-destructuring // 编译解构赋值
transform-es2015-duplicate-keys // 编译对象中重复的key,其实是转换成计算对象属性
transform-es2015-for-of // 编译for...of
transform-es2015-function-name // 将function.name语义应用于所有的function
transform-es2015-literals // 编译整数(8进制/16进制)和unicode
transform-es2015-modules-commonjs // 将modules编译成commonjs
transform-es2015-object-super // 编译super
transform-es2015-parameters // 编译参数,包括默认参数,不定参数和解构参数
transform-es2015-shorthand-properties // 编译属性缩写
transform-es2015-spread // 编译展开运算符
transform-es2015-sticky-regex // 正则添加sticky属性
transform-es2015-template-literals // 编译模版字符串
transform-es2015-typeof-symbol // 编译Symbol类型
transform-es2015-unicode-regex // 正则添加unicode模式
transform-regenerator // 编译generator函数
babel-preset-es2016
es2016(ES7)相关方法转译使用的插件,具体见文档。
transform-exponentiation-operator // 编译幂运算符
babel-preset-es2017
es2017(ES8)相关方法转译使用的插件,具体见文档。
syntax-trailing-function-commas // function最后一个参数允许使用逗号
transform-async-to-generator // 把async函数转化成generator函数
babel-preset-latest
latest是一个特殊的presets,包括了es2015,es2016,es2017的插件,不过已经废弃,使用babel-preset-env代替,具体见文档。
stage-x(stage-0/1/2/3/4)
stage-x预设中的任何转换都是尚未被批准为发布Javascript的语言(如ES6 / ES2015)的更改。
stage-x和上面的es2015等有些类似,但是它是按照JavaScript的提案阶段区分的,一共有5个阶段。而数字越小,阶段越靠后,存在依赖关系。也就是说stage-0是包括stage-1的,以此类推。
babel-preset-stage-4:
stage-4的插件:
syntax-trailing-function-commas // function最后一个参数允许使用逗号(ES8已经存在)
transform-async-to-generator // 把async函数转化成generator函数(ES8已经存在)
transform-exponentiation-operator // 编译幂运算符(ES7已经存在)
babel-preset-stage-3:
除了stage-4的内容,还包括以下插件:
transform-object-rest-spread // 编译对象的解构赋值和不定参数
transform-async-generator-functions // 将async generator function和for await编译为es2015的generator。
babel-preset-stage-2:
除了stage-3的内容,还包括以下插件:
syntax-dynamic-import // 动态加载模块
transform-class-properties // 编译静态属性(es2015)和属性初始化语法声明的属性(es2016)。
已禁用的等待提案更新(可以在此期间使用旧版转换)
babel-preset-stage-1:
除了stage-2的内容,还包括以下插件:
transform-class-constructor-call(弃用) // 编译class中的constructor,在Babel7中会被移除
transform-export-extensions // 编译额外的export语法,如export * as ns from "mod";细节可以看这个。
babel-preset-stage-0:
除了stage-1的内容,还包括以下插件:
transform-do-expressions // 编译do表达式
transform-function-bind // 编译bind运算符,即
::
为了方便,我们暂时引用 babel-preset-env 和babel-preset-stage-2这两个预设。为了启用预设,必须在.babelrc文件中定义预设的相关配置,这里参考vue-cli 模板中的配置。
安装:
npminstall --save-dev babel-preset-env babel-preset-stage-2
.babelrc配置说明:
{
"presets": [
["env", {
"modules": false
}],
"stage-2"
]
}
Babel 插件(plugins)
我们看一下预设的构成就知道,其实就是plugins的组合。如果你不采用presets,完全可以单独引入某个功能,比如以下的设置就会引入编译箭头函数的功能,在.babelrc文件中进行配置:
{
"plugins": ["transform-es2015-arrow-functions"]
}
babel-polyfill 与 babel-runtime
Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转码。
举例来说,ES6在 Array 对象上新增了 Array.from 方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用 babel-polyfill ,为当前环境提供一个垫片。babel-polyfill 是对浏览器缺失API的支持。
babel-runtime 是为了减少重复代码而生的。 babel生成的代码,可能会用到一些_extend(), classCallCheck() 之类的工具函数,默认情况下,这些工具函数的代码会包含在编译后的文件中。如果存在多个文件,那每个文件都有可能含有一份重复的代码。babel-runtime插件能够将这些工具函数的代码转换成require语句,指向为对babel-runtime的引用,如require('babel-runtime/helpers/classCallCheck')
. 这样, classCallCheck的代码就不需要在每个文件中都存在了。
启用插件 babel-plugin-transform-runtime 后,Babel 就会使用 babel-runtime 下的工具函数。除此之外,babel 还为源代码的非实例方法(Object.assign,实例方法是类似这样的 "foobar".includes("foo"))和 babel-runtime/helps 下的工具函数自动引用了 polyfill。这样可以避免污染全局命名空间,非常适合于 JavaScript 库和工具包的实现。
总结:
具体项目还是需要使用 babel-polyfill,只使用 babel-runtime 的话,实例方法不能正常工作(例如 "foobar".includes("foo"));
JavaScript 库和工具可以使用 babel-runtime,在实际项目中使用这些库和工具,需要该项目本身提供 polyfill。
transform-runtime只会对es6的语法进行转换,而不会对新api进行转换。如果需要转换新api,就要引入babel-polyfill。
安装插件
npm install --save-dev babel-plugin-transform-runtime
.babelrc 配置:
{
"plugins": ["transform-runtime", options]
}
options主要有以下设置项:
helpers: boolean,默认true,使用babel的helper函数;
polyfill: boolean,默认true,使用babel的polyfill,但是不能完全取代babel-polyfill;
regenerator: boolean,默认true,使用babel的regenerator;
moduleName: string,默认babel-runtime,使用对应module处理。
注:默认moduleName为babel-runtime,这里我们可以不必显式的下载babel-runtime,因为babel-plugin-transform-runtime依赖于babel-runtime。
babel-register
babel-register 模块改写 require 命令,为它加上一个钩子。此后,每当使用 require 加载 .js 、 .jsx 、 .es 和 .es6 后缀名的文件,就会先用Babel进行转码。引入babel-register,这样后面的文件就可以用 import 代替require,import的优点在于可以引入所需方法或者变量,而不需要加载整个模块,提高了性能。
安装:
npm install --save-dev babel-register
这部分我们又介绍了下面几个模块的安装:
babel-loader
babel-core
babel-preset-env
babel-preset-stage-2
babel-plugin-transform-runtime
babel-register
webpack 中如何使用 vue?
既然本文的目标是vue的自定义模板工程,那么自然这里需要单独介绍一下webpack中vue相关的插件。
Vue2文件比较
npm 安装:
npm install --save vue
vue2 经过 2.2 版本升级后, 文件变成 8 个:
独立构建 | vue.js | vue.common.js | vue.esm.js |
运行构建 | vue.runtime.js | vue.runtime.common.js | vue.runtime.esm.js |
vue.min.js 和 vue.runtime.min.js 都是对应的压缩版。
AMD:异步模块规范
没有单独提供 AMD 模块的版本,但是UMD版本中进行了包装,可以直接用作 AMD 模块,使用方法如下:
define(["Vue"],function(Vue) {
function myFn() {
...
}
return myFn;
});
CommonJS:
node中常用的模块规范,通过require引入模块,module.exports导出模块。
...
function Vue$3() {
...
}
...
module.exports = Vue$3;
UMD: 通用模块规范
兼容了AMD和CommonJS,同时还支持老式的“全局”变量规范:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Vue = factory());
}(this, (function () { 'use strict';
...
function Vue$3() {
...
}
...
return Vue$3;
})));
ES Module
ES6在语言标准的层面上,实现的模块功能。模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
...
function Vue$3() {
...
}
export default Vue$3;
总结:
vue.js 和 vue.runtime.js 可以用于直接 CDN 引用;
vue.common.js和vue.runtime.common.js可以使用Webpack1 / Browserify 打包构建;
vue.esm.js和vue.runtime.esm.js可以使用Webpack2 / rollup 打包构建。
vue有两种构建方式,独立构建和运行时构建。它们的区别独立构建前者包含模板编译器而运行构建不包含。模板编译器的职责是将模板字符串编译为纯 JavaScript 的渲染函数。如果你想要在组件中使用 template 选项,你就需要编译器。
独立构建包含模板编译器并支持 template 选项。 它也依赖于浏览器的接口的存在,所以你不能使用它来为服务器端渲染。
运行时构建不包含模板编译器,因此不支持 template 选项,只能用 render 选项,但即使使用运行时构建,在单文件组件中也依然可以写模板,因为单文件组件的模板会在构建时预编译为 render 函数。运行时构建比独立构建要轻量30%,只有 17.14 Kb min+gzip大小。
独立构建方式可以这样使用template选项:
import Vue from 'vue'
new Vue({
template: `
<div id="app">
<h1>Basic</h1>
</div>
`
}).$mount('#app')
这里我们使用ES Module规范,默认 NPM 包导出的是运行时构建。为了使用独立构建,在 webpack 配置中添加下面的别名:
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
vue-loader
安装:
npm install --save-dev vue-loader vue-template-compiler
vue-loader 依赖于 vue-template-compiler。
vue-loader 是一个 Webpack 的 loader,可以将用下面这个格式编写的 Vue 组件转换为 JavaScript 模块。这里有一些 vue-loader 提供的很酷的特性:
ES2015 默认支持;
允许对 Vue 组件的组成部分使用其它 Webpack loaders,比如对
<style>
使用 SASS 和对<template>
使用 Jade;.vue 文件中允许自定义节点,然后使用自定义的 loader 处理他们;
把
<style>
和<template>
中的静态资源当作模块来对待,并使用 Webpack loaders 进行处理;对每个组件模拟出 CSS 作用域;
支持开发期组件的热重载。
简而言之,编写 Vue.js 应用程序时,组合使用 Webpack 和 vue-loader 能带来一个现代,灵活并且非常强大的前端工作流程。
在 Webpack 中,所有的预处理器需要匹配对应的 loader。 vue-loader 允许你使用其它 Webpack loaders 处理 Vue 组件的某一部分。它会根据 lang 属性自动推断出要使用的 loaders。
上述我们提到extract-text-webpack-plugin插件提取css,这里说明一下.vue中style标签之间的样式提取的办法:
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
css: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader' // <- 这是vue-loader的依赖,所以如果使用npm3,则不需要显式安装
})
}
}
}
]
},
plugins: [
new ExtractTextPlugin("app.css")
]
}
pug 模板
用过模板的都知道,熟悉了模板写起来快多了,大名鼎鼎的jade恐怕无人不知吧。pug是什么鬼?第一次听到的时候我也好奇了,然后查了一下才知道,Pug原名不叫Pug,原来是大名鼎鼎的jade,后来由于商标的原因,改为Pug,哈巴狗。以下是官方解释:
简单看了看还是原来jade熟悉的语法规则,果断在这个模板工程里面用上。
vue-loader里面对于模版的处理方式略有不同,因为大多数 Webpack 模版处理器(比如 pug-loader)会返回模版处理函数,而不是编译的 HTML 字符串,我们使用原始的 pug 替代 pug-loader:
npm install pug --save-dev
使用:
<template lang="pug">
div
h1 Hello world!
</template>
PostCSS
安装vue-loader的时候默认安装了postcss,由vue-loader处理的 CSS 输出,都是通过PostCSS进行作用域重写,你还可以为 PostCSS 添加自定义插件,例如autoprefixer或者CSSNext。
在 webpack 工程中使用 postcss,我们需要下载 postcss-loader:
npm install --save-dev postcss-loader
cssnext
安装:
npm install --save-dev postcss-cssnext
postcss.config.js:
module.exports = {
plugins: [
require('postcss-cssnext')
]
}
webpack.config.js:
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}
]
}
}
cssnext 依赖了autoprefixer,所以我们无需显式下载autoprefixer。更多关于postcss的插件可以看这里:postcss plugins。
这一部分我们学习了这些依赖:
vue
vue-loader
vue-template-compiler
pug
postcss-loader
postcss-cssnext
webpack2 开启 eslint 校验
规范自己的代码从ESlint开始。ESlint和webpack集成,在babel编译代码开始前,进行代码规范检测。这里我们使用javascript-style-standard风格的校验。
主要依赖的几个包:
eslint —— 基础包
eslint-loader —— webpack loader
babel-eslint —— 校验babel
eslint-plugin-html —— 提取并检验你的 .vue 文件中的 JavaScript
eslint-friendly-formatter —— 生成美化的报告格式
# javascript-style-standard 依赖的包
eslint-config-standard
eslint-plugin-import
eslint-plugin-node
eslint-plugin-promise
eslint-plugin-standard
安装:
npm install --save-dev eslint eslint-loader babel-eslint eslint-plugin-html eslint-friendly-formatter eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-node eslint-plugin-promise eslint-plugin-standard
关于eslint的配置方式,比较多元化,具体可以看配置文档:
js注释
.eslintrc.*文件
package.json里面配置eslintConfig字段
安装eslint-loader之后,我们可以在webpack配置中使用eslint加载器。webpack.config.js
...
module: {
loaders: [
{
test: /\.vue|js$/,
enforce: 'pre',
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/,
use: [{
loader: 'eslint-loader',
options: {
formatter: require('eslint-friendly-formatter')
}
}]
}
]
},
...
此外,我们既可以在webpack配置文件中指定检测规则,也可以遵循最佳实践在一个专门的文件中指定检测规则,我们就采用后面的方式。
在根目录下:
touch .eslintrc.js
.eslintrc.js:
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true
},
extends: 'standard',
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
rules: {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}
这部份我们主要学习了一下eslint相关插件的含义和配置方法。
创建属于你的模板
如果你对官方的模板不感兴趣,你可以自己fork下来然后进行修改(或者重新写一个),然后用 vue-cli 来调用。因为 vue-cli 可以直接拉取 git源:
vue init username/repo my-project
这里我们参考vue-cli的模板工程自己写一个模板工程,主要是需要通过meta.*(js,json)进行配置:
module.exports = {
"helpers": {
"if_or": function (v1, v2, options) {
if (v1 || v2) {
return options.fn(this);
}
return options.inverse(this);
}
},
"prompts": {
"name": {
"type": "string",
"required": true,
"message": "Project name"
},
"version": {
"type": "string",
"required": false,
"message": "Project version",
"default": "1.0.0"
},
"description": {
"type": "string",
"required": false,
"message": "Project description",
"default": "A Vue.js project"
},
"author": {
"type": "string",
"message": "Author"
},
"router": {
"type": "confirm",
"message": "Install vue-router?"
},
"vuex": {
"type": "confirm",
"message": "Install vuex?"
}
},
"completeMessage": "To get started:\n\n {{^inPlace}}cd {{destDirName}}\n {{/inPlace}}npm install\n npm run dev\n\nDocumentation can be found at https://github.com/zhaomenghuan/vue-webpack-template"
};
这里我们就是采用最简单的方式,对于vue-router、vuex的配置每个人习惯不一样,所以不写在模板工程里面。
然后使用vue-cli使用这个模板创建工程,没有安装vue-cli的执行:
npm install --global vue-cli
然后创建工程:
# 创建一个基于 webpack 模板的新项目
vue init zhaomenghuan/vue-webpack-template my-project
# 安装依赖,走你
cd my-project
npm install
npm run dev
参考
webpack官方文档
babel官方文档
vue-loader中文文档
JavaScript books by Dr. Axel Rauschmayer
ES7新特性及ECMAScript标准的制定流程
如何写好.babelrc?Babel的presets和plugins配置解析
babel的polyfill和runtime的区别
webpack2集成eslint