前言

webpack2和vue2已经不是新鲜东西了,满大街的文章在讲解webpack和vue,但是很多内容写的不是很详细,对于很多个性化配置还是需要自己过一遍文档。Vue官方提供了多个vue-templates,基于vue-cli用官方的webpack模板居多,不过对于很多人来说,官方的webpack模板的配置还是过于复杂,对于我们了解细节实现不是很好,所以想自己从零开始搭建一个模板工程,也顺便重新认识一下webpack和vue工程化的细节。

webpack 核心概念

官方网站:https://webpack.js.org/

安装

在开始前,先要确认你已经安装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-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 TC39TC39(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的插件:

babel-preset-stage-3:

除了stage-4的内容,还包括以下插件:

babel-preset-stage-2:

除了stage-3的内容,还包括以下插件:

babel-preset-stage-1:

除了stage-2的内容,还包括以下插件:

babel-preset-stage-0:

除了stage-1的内容,还包括以下插件:

为了方便,我们暂时引用 babel-preset-envbabel-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.jsvue.common.jsvue.esm.js
运行构建vue.runtime.jsvue.runtime.common.jsvue.runtime.esm.js

vue.min.js 和 vue.runtime.min.js 都是对应的压缩版。

  • AMD:异步模块规范

  1. 没有单独提供 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


03-05 13:45