38

前言

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

webpack 核心概念

Webpack 是当下最热门的前端资源模块化管理和打包工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。通过 loader 的转换,任何形式的资源都可以视作模块,比如 CommonJs 模块、 AMD 模块、 ES6 模块、CSS、图片、 JSON、Coffeescript、 LESS 等。

官方网站: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)

入口起点告诉 webpack 从哪里开始,并遵循着依赖关系图表知道要打包什么。可以将您应用程序的入口起点认为是根上下文(contextual root)或 app 第一个启动文件。

单个入口(简写)语法:
用法: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)

将所有的资源(assets)归拢在一起后,还需要告诉 webpack 在哪里打包应用程序。webpack 的 output 属性描述了如何处理归拢在一起的代码(bundled code)。output 选项控制 webpack 如何向硬盘写入编译文件。注意,即使可以存在多个入口起点,但只指定一个输出配置。

在 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 用于对模块的源代码进行转换。loader 可以使你在 require() 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你在 JavaScript 中 require() CSS文件!

在你的应用程序中,有三种方式使用 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)

由于 loader 仅在每个文件的基础上执行转换,而插件(plugins)最常用于(但不限于)在打包模块的“compilation”和“chunk”生命周期执行操作和自定义功能(查看更多)。webpack 的插件系统极其强大和可定制化

想要使用一个插件,你只需要 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 个:

UMD CommonJS ES Module
独立构建 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:异步模块规范

  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,哈巴狗。以下是官方解释:

it has been revealed to us that "Jade" is a registered trademark, and as a result a rename is needed. After some discussion among the maintainers, "Pug" has been chosen as the new name for this project.

简单看了看还是原来jade熟悉的语法规则,果断在这个模板工程里面用上。

vue-loader里面对于模版的处理方式略有不同,因为大多数 Webpack 模版处理器(比如 pug-loader)会返回模版处理函数,而不是编译的 HTML 字符串,我们使用原始的 pug 替代 pug-loader:

npm install pug --save-dev

使用:

<template lang="pug">
div
  h1 Hello world!
</template>

重要: 如果你使用 vue-loader@<8.2.0, 你还需要安装 template-html-loader

PostCSS

安装vue-loader的时候默认安装了postcss,由vue-loader处理的 CSS 输出,都是通过PostCSS进行作用域重写,你还可以为 PostCSS 添加自定义插件,例如autoprefixer或者CSSNext

在 webpack 工程中使用 postcss,我们需要下载 postcss-loader:

npm install --save-dev postcss-loader

cssnext

cssnext 是一个 CSS transpiler,允许你使用最新的 CSS 语法。cssnext 把 新 CSS 规范转换成兼容性更强的 CSS,所以不需要等待各种浏览器支持。

安装:

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

这里按照国际惯例安利一下本文的模板工程:vue-webpack-template

参考

webpack官方文档
babel官方文档
vue-loader中文文档
JavaScript books by Dr. Axel Rauschmayer
ES7新特性及ECMAScript标准的制定流程
如何写好.babelrc?Babel的presets和plugins配置解析
babel的polyfill和runtime的区别
webpack2集成eslint

clipboard.png


近期在segmentfault讲堂开设了一场关于html5+ App开发工程化实践之路的讲座,欢迎前来围观:https://segmentfault.com/l/15...


匠心
4.6k 声望1.5k 粉丝

看似寻常最奇崛,成如容易却艰辛。