基于 Webpack 3 的 Vue.js 工程项目脚手架 - 知乎专栏

1,221 阅读9分钟
原文链接: zhuanlan.zhihu.com

基于 Webpack 3 的 Vue.js 工程项目脚手架从属于笔者的 Web 前端入门与工程实践,是笔者基于兄弟项目 React 脚手架改造而来,二者在 Webpack 配置层面差异不大。更多关于 Vue.js 或者前端开发相关的资料链接可以参考Vue.js 学习与实践资料索引Vue.js 与前端工程化实践系列文章以及 Webpack 学习与资料索引,对于其中浅薄的工程化的思考可以参考 2016-我的前端之路:工具化与工程化

基于 Webpack 3 的 Vue.js 工程项目脚手架

create-webpack-app 是笔者对于日常工作中的基于 React/Vue.js 技术栈与实践的沉淀,dev-config/* 与 package.json 构成了基础的脚手架,支持最新的开发流程与默认的生产环境优化;模板项目包含特性如下:

  • 技术栈支持:使用 ES6/ES7 语法、允许使用 CSS Modules、SCSS、Less 并且使用 PostCSS 进行自动 Polyfill、支持使用 styled-component 进行 CSS-in-JS 样式控制、使用 Flow 作为静态类型检测工具、使用 Jest 作为默认的测试框架

  • 开发环境:使用 WebpackDevServer 部署开发服务器、使用 React Hot Loader 进行组件热加载、使用 Babel 进行代码转换、使用 ESLint 进行代码检测、使用 DllPlugin 作为开发环境下公共代码提取工具以优化编译速度

  • 生产环境:使用 CommonChunksPlugin 作为生产环境下公共代码提取工具、使用 Prepack & prepack-webpack-plugin 进行代码优化、使用 offline-plugin 添加简单的 PWA 特性增强

  • 部署方式:支持独立部署(Hash 方式切换路由)、支持服务端部署、支持服务端渲染

本部分即是针对 Vue.js 项目的脚手架,我们可以直接拷贝该项目来展示部分开发模式或者作为模板项目使用:

# 下载本项目
git clone https://github.com/wxyyxc1992/create-webpack-app

# 可以使用 yarn install & npm start 直接运行本项目
# 仅保留 dev-config、package.json、src/client.js、src/ssr_server.js
mkdir /path/to/your/project

# 拷贝必须的启动文件
cp -r vue/dev-config/ /path/to/your/project
cp vue/package.json /path/to/your/project
cp vue/src/client.js /path/to/your/project/src/

# 安装运行依赖
cd /path/to/your/project

yarn install / npm install

# 有时候需要安装 better-npm-run
npm install better-npm-run -g

# 启动项目
npm start

# 编译为纯客户端部署模式,即单个 HTML 页面
npm run build

# 进行依赖升级检查
npm run update
  • 开发环境下的操作面板:

  • 编译之后的包体分析:


本模板相较于官方的 webpack-simple 功能稍微复杂,并且引入了完整的 Flow、Jest 等技术栈配置,同时优化了 Webpack 的构建性能。

!Important! 项目尚未支持 SSR

基础配置

create-webpack-app 默认的应用配置位于 dev-config/apps.config.js 文件中,该文件也是 dev-config/ 文件夹下唯一与应用业务相关的文件;该文件定义了不同应用中的需要配置的应用相关信息。create-webpack-app 定位为单项目多应用的模板,因此我们可以在apps 键下配置项目设计的应用入口;在打包时会自动将多个应用并行编译并且提取出所有公共的代码。每个应用需要提供唯一编号、入口文件地址、模板页面、是否编译等信息;接下来 devServer 则是定义了当前正在开发的应用入口,ssrServer 定义了打包时需要使用的渲染服务器入口,其会在执行 npm run build:ssr 时调用,proxy 与 api 则定义了后端服务器信息,开发者可以根据业务需求自行使用。典型的 apps.config.js 文件配置如下:

module.exports = {
  //基本的应用配置信息
  apps: [
    //HelloWorld
    {
      id: "pwa",
      src: "./pwa/client.js",
      indexPage: defaultIndexPage,
      compiled: true
    }
  ],

  //开发入口配置
  devServer: {
    appEntrySrc: "./pwa/client.js", //当前待调试的APP的入口文件
    port: 3000 //监听的Server端口
  },

  //用于服务端渲染的Server路径
  ssrServer: {
    serverEntrySrc: "./pwa/ssr_server.js"
  },

  //依赖项配置
  proxy: {
    //后端服务器地址 http://your.backend/
    "/api/*": "http://localhost:3001"
  },

  //后端 api 配置,这样配置可以避免将测试服务器端口暴露出去
  api: {
    dev: {},
    prod: {}
  }
};

应用入口文件则遵循官方的单文件组件范式:

import Vue from 'vue';
import App from './application/App.vue';

new Vue({
  el: '#root',
  render: h => h(App)
});

脚本编译与热加载

在 dev-config/webpack/loaders.js 文件中定义了模板所需要的加载器,默认支持 js、jsx、vue、ts、tsx、css、scss、less、json 以及各种资源文件等常见格式。当我们执行 npm start 命令时,会自动启动dev-config/server/devServer.js 文件中定义的 Webpack 开发服务器,该服务器会使用 dev-config/webpack.config.js 文件进行配置项生成。值得一提的是,WebpackDevServer 中的 contentBase 设置为了 path.join(__dirname, "../../public"),也就是将 /public 目录作为开发服务器的默认根目录。热加载配置包括以下步骤:

  • 开发时应用入口设置:

  entry = [
    `webpack-dev-server/client?http://0.0.0.0:${appsConfig.devServer.port}`,
    "webpack/hot/only-dev-server",
    require("./apps.config.js").devServer.appEntrySrc
  ];
  • Babel 配置,默认的 Babel 文件位于 dev-config/tool/.babelrc:

  ...
  "plugins": [
  ...

样式处理

create-webpack-app 支持 SCSS、CSS Modules 以及 styled-components 这三种样式定义方式。

Webpack 性能优化

公共代码分割

create-webpack-app 使用了 CommonsChunkPlugin 进行代码分割,默认在 dev-config/webpack/plugins.js 文件中定义了对于 node_modules 中依赖文件的自动抽取:

  new webpack.optimize.CommonsChunkPlugin({
    name: "vendor",
    filename: "vendor.bundle.js",
    minChunks: ({ resource }) =>
      resource &&
      resource.indexOf("node_modules") >= 0 &&
      resource.match(/\.(js|less|scss)$/)
  })

该插件会自动生成 vendor.bundle.js 文件,我们需要在应用入口文件之前引用它;开发者也可以自定义 CommonsChunkPlugin 插件以自定义需要提取的公共代码。

构建性能优化

随着项目复杂度与体量的增加,我们发现初始化编译与增量编译的速度都有所下降,为了提升构建性能首先我们要做的就是保持 Webpack 版本的更新速度;此外,create-webpack-app 还默认启动了 DllPlugin 在开发状态下将所有的依赖提取为 dll 文件以提高增量编译的速度。因为考虑到灵活性,即随时有可能增减依赖的情况,create-webpack-app 目前设置的是每次使用 npm start 的时候都会重新生成 dll 文件;如果是已经稳定的项目可以考虑仅生成一次依赖。

const path = require("path");
const pkg = require("../package.json");
const webpack = require("webpack");

let dllConfig = {
  name: "vendor",
  entry: Object.keys(pkg.dependencies),
  output: {
    path: path.resolve(__dirname, "../public/dll"),
    filename: "vendor.bundle.js",
    library: "vendor_[hash]"
  },
  plugins: [
    new webpack.DllPlugin({
      name: "vendor_[hash]",
      path: path.resolve(__dirname, "../public/dll/manifest.json")
    })
  ]
};

module.exports = dllConfig;

// 在 public/index.html 文件中需要引入该依赖
// index.html
<script src="dll/vendor.bundle.js"></script>

代码编译优化

create-webpack-app 中也内置了其他的编译之后的代码性能优化插件,首先是利用 Webpack 3 的 Scope Hositing 特性来优化生成的模块;这一点需要使用 ModuleConcatenationPlugin 插件。此外,还使用了 PrepackWebpackPlugin 对于打包生成的文件进行过滤与重构;不过需要注意的是 PrepackWebpackPlugin 会较大地降低编译速度,因此也是可以根据实际的项目情况选用。

  // 使用 Scope Hositing 特性
  new webpack.optimize.ModuleConcatenationPlugin(),

  // 使用 Prepack 优化包体大小
  // 暂时存在 Bug,等待修复
  // 使用前 21 - 425
  // 使用后 21 - 433
  new PrepackWebpackPlugin({
    mathRandomSeed: "0"
  }),

PWA

create-webpack-app 中只是简单地使用了 Offline Plugin,其配置如下:

// webpack.config.js example

var OfflinePlugin = require('offline-plugin');

module.exports = {
  // ...

  plugins: [
    // ... other plugins
    // it's always better if OfflinePlugin is the last plugin added
    new OfflinePlugin()
  ]
  // ...
}

// render.js
require('offline-plugin/runtime').install();

观察网络面板中的资源请求情况,我们可以看到脚本等已经被缓存在了本地:

设计模式

JavaScript 优先

本小节仅代表本人个人意见,请多多指点

官方文档中介绍的单文件组件(Single File Component)包含了 template、script、style 这三个部分,笔者感觉算是典型的前端项目三要素。不过笔者习惯的开发模式是以 JavaScript 为中心,即 JavaScript 文件单独可测试,而不是和样式以及标签混合在一起(JSX 本质上也是 JavaScript)。因此在该模板中,笔者是将标签、样式与脚本分到了三个文件中:

// App.js
// @flow

export default {
  name: "app",
  data() {
    return {
      msg: "Vue.js & Webpack App Boilerplate by 王下邀月熊"
    };
  }
};

// App.scss
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

h1, h2 {
  font-weight: normal;
}

ul {
  list-style-type: none;
  padding: 0;
}

li {
  display: inline-block;
  margin: 0 10px;
}

a {
  color: #42b983;
}

// App.vue
<template>
    <div id="app">
        <img src="../../public/assets/logo.png">
        <h1>{{ msg }}</h1>
        <h2>Essential Links</h2>
        <ul>
            <li><a href="https://vuejs.org" target="_blank">Core Docs</a></li>
            <li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li>
            <li><a href="https://gitter.im/vuejs/vue" target="_blank">Gitter Chat</a></li>
            <li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
        </ul>
        <h2>Ecosystem</h2>
        <ul>
            <li><a href="http://router.vuejs.org/" target="_blank">vue-router</a></li>
            <li><a href="http://vuex.vuejs.org/" target="_blank">vuex</a></li>
            <li><a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li>
            <li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
        </ul>
    </div>
</template>

<script>
  import App from './App.js';
  export default App;
</script>

<style lang="scss">
    @import "App";
</style>

笔者个人感觉这种模式更符合单一职责原则,对于复杂的组件能够提高代码可读性;同时将 JavaScript 代码独立出来也能更加方便地进行单元测试与类型检测等操作。

代码风格

详细的 JavaScript 编程样式指南已经迁移到了 Web 项目开发风格指南 JavaScript 编程样式指南,涵盖了基本原则阐述、代码风格、代码格式化与语法检测、项目架构等几个部分。不过本部分建议是类似于 Create React APP 配置提交时自动进行格式化,首先需要安装如下依赖:

npm install --save husky lint-staged prettier
// or
yarn add husky lint-staged prettier

然后在 package.json 中添加 Hook:

  "scripts": {
    "precommit": "lint-staged",
    ...

同时添加 lint-staged 配置:

  "dependencies": {
    // ...
  },
+ "lint-staged": {
+   "{src,stories}/**/*.{js,jsx,json,css}": [
+     "prettier --single-quote --write",
+     "git add"
+   ]
+ },
  "scripts": {

这样当我们提交代码时就会自动使用 Prettier 优化代码,不过需要注意的是这种配置仅作用于根目录下;如果某个仓库中包含了多个应用配置,那么我们还需要在根目录下单独配置脚本。我们也可以使用 ./node_modules/.bin/prettier --single-quote --write "src/**/*.{js,jsx}" 来手动进行项目文件的格式化。另外因为模板项目时放置在了子文件下,如果使用者希望使用该特性需要手动地在 package.json 中添加 "precommit": "lint-staged" 这个配置。