首发于easyjs
Webpack工程化解决方案easywebpack

Webpack工程化解决方案easywebpack

Github:github.com/hubcarl/easy


1.背景

随着越来越多的项目采用vue, react, weex进行业务开发, 在前端构建方面大多数是用webpack进行构建。但存在以下问题:

  • 各个项目都是自己从零编写webpack配置,存在很多定制性的配置,无法复用,大多都是复制拷贝。
  • Webpack 配置项多,正式环境,js/css/image压缩,css extract, css module, sass, less, stylus, postcss, babel, cdn,单页面,多页面,热更新, 前端渲染,服务器渲染等特性时,配置非常复杂。

在前端工程构建方面迫切需要一套基于webpack的通用且可扩展性强的前端工程化解决方案.


2.我们要解决什么问题

针对背景里面提到的一些问题, 基于webpack + egg项目的工程化, 当初想到和后面实践中遇到问题, 主要有如下问题需要解决:

  • Vue服务端渲染性能如何?
  • webpack 客户端(browser)运行模式打包支持
  • webpack 服务端(node)运行模式打包支持
  • 如何实现服务端和客户端代码修改webpack热更新功能
  • webpack打包配置太复杂(客户端,服务端), 如何简化和多项目复用
  • 开发, 测试, 正式等多环境支持, css/js/image的压缩和hash, cdn等功能如何配置, 页面依赖的css和js如何加载
  • 如何快速扩展出基于vue, react前端框架服务端和客户端渲染的解决方案

3.Webpack工程化设计

我们知道webpack是一个前端打包构建工具, 功能强大, 意味的配置也会复杂. 我们可以通过针对vue, react等前端框架,采用不同的配置构建不同的解决方案. 虽然这样能实现, 但持续维护的成本大, 多项目使用时就只能采用拷贝的方式, 另外还有一些优化和打包技巧都需要各自处理.

基于以上的一些问题和想法, 我希望基于webpack的前端工程方案大概是这个样子:

  • webpack太复杂, 项目可重复性和维护性低, 是不是可以把基础的配置固化, 然后基于基础的配置扩展出具体的解决方案(vue/react等打包方案).
  • webpack配置支持多环境配置, 根据环境很方便的设置是否开启source-map, hash, 压缩等特性.
  • webpack配置的普通做法是写配置, 是不是可以采用面向对象的方式来编写配置.
  • 能够基于基础配置很简单的扩展出基于vue, react 服务端渲染的解决方案
  • 针对egg + webpack内存编译和热更新功能与框架无关, 可以抽离出来, 做成通用的插件

4.设计实现

4.1. Webpack 基础配置固化

在使用webpack对不同的前端框架进行打包和处理时, 有些配置是公共的, 有些特性是共性的, 我们把这些抽离出来, 并提供接口进行设置和扩展.

4.2 公共配置

  • option: entry读取, output, extensions 等基础配置
  • loader: babel-loader, json-loader, url-loader, style-loader, css-loader, sass-loader, less-loader, postcss-loader, autoprefixer 等
  • plugin: webpack.DefinePlugin(process.env.NODE_ENV), CommonsChunkPlugin等

4.3 公共特性

  • js/css/image 是否hash
  • js/css/image 是否压缩
  • js/css commonChunk处理

4.4 开发辅助特性

  • 编译进度条插件 ProgressBarPlugin
  • 资源依赖表 ManifestPlugin
  • 热更新处理 HotModuleReplacementPlugin
  • ……

以上一些公共特性是初步梳理出来的, 不与具体的前端框架耦合. 针对这些特性可以单独写成一个npm组件, 并提供扩展接口进行覆盖, 删除和扩展功能.

在具体实现时, 可以根据 env 默认开启或者关闭一些特性. 比如本地开发时, 关闭 js/css/image 的hash和压缩,开启热更新功能.


5.easywebpack功能介绍

easywebpack 是在 Webpack 上扩展出来的前端项目构建工程化解决方案, 同时支持 Vue,React 服务端端渲染构建,也支持 Weex Nativ 和 Web 构建. 同时内置 Webpack 常用功能和基础插件,支持插件动态安装,功能开启,框架扩展等特性。

目前 easywebpack 已支持 Webpack3(easywebpack 3.x.x) 和 Webpack2(easywebpack 1.x.x),很多新特性可以马上尝鲜了。另外支持 Mac 和 Window 系统(被 Windows 坑了好久,如果可以,尽可能用 Mac)。

  • 首先我们看看 easywebpack 具备的基础功能:


  • 针对上面梳理的公共基础配置, 可以把webpack配置分离成三部分: option, loader, plugin
  • 针对客户端和服务端打包的差异性, 设计成三个类 WebpackBaseBuilder, WebpackClientBuilder, WebpackServerBuilder



最终形成Webpack构建解决方案:easywebpack


5.1 基础功能

  • 支持服务端渲染, 前端渲染, 静态页面渲染三种构建方式
  • 支持单页面, 多页面服务端渲染构建模式
  • 默认支持 dev,test, prod 环境配置
  • 集成 webpack-hot-middleware 热更新实现
  • 支持 entry 原生配置和目录遍历自动构造 entry 功能
  • 支持自动根据后缀名构建 entry 文件,比如 .vue 和 .jsx 文件为入口文件
  • 支持 es6 class 继承方式编写 Webpack 配置
  • 支持 js/css/image 压缩, 内置支持 CDN 特性
  • 支持 css/sass/less/stylus, 支持css module 和 css extract 特性
  • 支持 loader 是否启用,合并,覆盖配置
  • 支持 plugin 是否启用,合并,覆盖配置
  • 支持 loader 和 plugin npm module 是否启用,按需安装
  • 支持 eslint, postcss 等特性
  • 提供 easywebpack-cli 和 webpack-tool 辅助工具。

5.2 内置loader

  • babel-loader
  • eslint-loader
  • style-loader
  • css-loader
  • postcss-loader
  • sass-loader
  • less-loader
  • stylus-loader
  • url-loader

5.3 内置plugin

  • extract-text-webpack-plugin
  • npm-install-webpack-plugin
  • webpack.optimize.ModuleConcatenationPlugin
  • webpack.NoEmitOnErrorsPlugin
  • webpack.ProvidePlugin
  • webpack.DefinePlugin
  • webpack.optimize.CommonsChunkPlugin
  • webpack.optimize.UglifyJsPlugin
  • webpack.HotModuleReplacementPlugin
  • progress-bar-webpack-plugin
  • imagemin-webpack-plugin
  • directory-named-webpack-plugin
  • webpack.NormalModuleReplacementPlugin
  • webpack.IgnorePlugin
  • html-webpack-plugin

6.已有解决方案

基于 easywebpack 基础骨架,目前已扩展 Vue React Weex 三种解决方案,其中 easywebpack-vue 和 easywebpack-react 支持纯前端构建和Node端构建模式,easywebpack-weex 支持 Native 和 Web 构建模式。

如果你需要基于 easywebpack 扩展其他解决方案也很简单, 只需要继承 easywebpack 的 WebpackClientBuilder(前端渲染构建模式) 和 WebpackServerBuilder(服务端渲染构建模式) 即可, 你只需要把框架相关的扩展进来即可。 大概实现如下:

7.解决方案扩展实现

7.1 前端渲染构建模式

const EasyWebpack = require('easywebpack');
class WebpackClientBuilder extends EasyWebpack.WebpackClientBuilder {
constructor(config) {
super(config);
// call below api custom client builder
}
}
module.exports = WebpackClientBuilder;

7.2 服务端渲染构建模式

const EasyWebpack = require('easywebpack');
class WebpackServerBuilder extends EasyWebpack.WebpackServerBuilder {
constructor(config) {
super(config);
// call below api custom server builder
}
}
module.exports = WebpackServerBuilder;

具体实现请参考 easyebpack-vue`,`easyebpack-react, easyebpack-weex

8.命令行工具

8.1 easywebpack-cli 命令行工具

  • Vue, React, Weex 骨架项目初始化工具, 支持纯前端项目和Egg项目
  • 提供命令行 easywebpack 或 easy 命令编译项目和启动静态功能
  • 依赖 webpack-tool 工具构建

8.1.1 特性

8.1.2 安装

$ npm i easywebpack-cli -g

8.1.3 运行

easywebapck -h

Usage: easywebpack [command] [options]
Options:

-V, --version output the version number
-f, --filename [path] webpack config file name, default webpack.config.js
-w, --watch webpack watch and hot-update
-m, --hash webpack md5 hash js/css/image
-c, --compress webpack compress js/css/image
-b, --build [option] w(watch), m(hash) , c(compress), ex: wm/wc/mc/wmc
-h, --help output usage information

Commands:

init [options] init webpack config or boilerplate for Vue/React/Weex
install npm install
print [env] [options] print webpack config, support print by env or config node key
build [env] webpack building
server [env] webpack building and start server

8.1.4. 命令介绍

1. 配置模板和Boilerplate初始化

  • easywebpack init
step one:


step two:


2. 编译举例

  • easywebpack build
  • easywebpack build -f build/webpack.config.js
  • easywebpack build -c
  • easywebpack build dev
  • easywebpack build test
  • easywebpack build prod
  • easywebpack build -b wmc

默认读取项目根目录下的 webpack.config.js 配置

3. 编译和启动服务举例

  • easywebpack server
  • easywebpack server -f build/webpack.config.js
  • easywebpack server dev
  • easywebpack server test
  • easywebpack server prod
  • easywebpack server -b wmc

默认读取项目根目录下的 webpack.config.js 配置

4. 打印配置

easywebpack print -h

Usage: print [env] [options]
print webpack config, support print by env or config node key

Options:
-n, --node [key] print webpack config info by config node key, example: [module/module.rules/plugins] and so on
-h, --help output usage information

  • easywebpack print -n module
  • easywebpack print dev -n entry
  • easywebpack print test -n module.rules
  • easywebpack print prod -n module.rules[0]
  • easywebpack print -n plugins
  • easywebpack print -n plugins[0]
  • easywebpack print -n output
  • easywebpack print -n resolve

默认读取项目根目录下的 webpack.config.js 配置
GitHub:github.com/hubcarl/easy


8.2 webpack-tool 命令行工具

webpack-tool 是一个纯粹的 Webpack 构建工具, 不依赖任何框架, 支持以下特性:

  • 提供 Webpack 配置编译功能
  • 提供 Webpack 编译结果文件UI视图导航和访问功能

使用

//build/index.js
const WebpackTool = require('webpack-tool');
const weexNativeConfig = require('./weex/native');
const weexWebConfig = require('./weex/web');
const NODE_ENV = process.env.VIEW;
const webpackConfig = [weexNativeConfig, weexWebConfig];
const webpackTool = new WebpackTool();
if (NODE_ENV === 'development') {
  // start webpack build and show build result ui view
  webpackTool.server(webpackConfig);
} else {
  webpackTool.build(webpackConfig);
}


9.项目构建方案配置案例

9.1 纯前端项目配置和构建

假如要实现基于 Vue 或者 React 实现一个纯前端渲染项目改如何配置webpack.config.js.

9.1.1 项目结果要求如下:

  • 支持单页面和多页面entry配置
  • 支持根据 .vue 或者 .react 构建入口文件
  • 支持根据目录遍历, 项目page根目录为 page
  • 支持热更新,支持css extract, 支持 css和sass(默认支持),支持构建预览
  • 支持cdn配置,支持构建完成回调用于编写自定义逻辑
  • 支持公共文件抽取,抽取文件默认为 vendor.js
  • 支持 es6 编写
  • 支持js/css/image压缩和hash
  • 支持eslint,babel, postcss
  • 支持dev(不压缩,无hash,支持热更新)和 prod(压缩hash,css exteract) 配置


9.1.2 基于 easywebpack-vue 和 easywebpack-cli

上面的要求 easywebpack-vue 都支持,其中 支持eslint,babel, postcss 和 autoprefixer是默认开启

  • 按照上面的截图新建好项目,或者可以通过 easywebpack-ci 工具初始化完成
  • 项目安装 easywebpack-vue 解决方案依赖
npm install easywebpack-vue --save-dev
  • 编写 ${project}/webpack.config.js配置
const BUILD_ENV = process.env.BUILD_ENV;
const cdn = BUILD_ENV === 'prod' ? { url: 'http://your.cdn.com'} : '',
module.exports = {
  type: 'client, // 指定只构建前端渲染
  framework: 'vue', // 支持 react/weex
  entry: {
    include: 'page',
    exclude: ['page/test'],
    template: 'view/layout.html'
  }
  alias: {
    asset: 'asset',
    component: 'component',
    framework: 'framework',
    store: 'store'
  },
  cdn,
  done(){ // 编译完成
  // 这里可以做你想做的事情哟,比如 打包上传 CDN
	if(cdn && cdn.url){
	}
  }
}

只需要配上面这么多, 就可以 Running 了,因为 easywebpack-vue 把 babel,postcss,sass都默认支持了,当然你可以扩展。

  • 命令行编译

首先请安装 easywebpack-ci 工具, 然后就可以用 easywebpack 或 easy 命令
npm install easywebpack-ci -g

  • 命令行启动运行
easywebpack server 或 easywebpack server dev
easywebpack server prod
  • 命令行编译,默认开发模式
easywebpack build 或 easywebpack build dev
easywebpack build prod
  • 获取 Webpack 配置结果
const EasyWebpack = require('easywebpack-vue');
const webpackConfigList = EasyWebpack.getWebpackConfig()

9.1.3 基于 easywebpack-react 和 easywebpack-cli

如果要用react实现类似功能, 请把上面 framework 改为 react

9.2 Vue/React Server Side Render 配置

如果要基于上面的要求实现一个 Vue /React 服务端渲染的构建配置, 该如何配置,非常简单。

9.2.1 构建配置

  • copy 一份上面的配置
  • 去掉 type:client 配置
  • 如果 react,framework配置改为 'framework: react', 另外安装 easywebpack-react 依赖
  • 添加 jsx file loader template, 请查看配置举例。
loader: {
  client: 'framework/vue/entry/client-loader.js',
  server: 'framework/vue/entry/server-loader.js'
}

完整结构如下:

const BUILD_ENV = process.env.BUILD_ENV;
const cdn = BUILD_ENV === 'prod' ? { url: 'http://your.cdn.com'} : '',
module.exports = {
  framework: 'vue', // 支持 react/weex
  entry: {
    include: 'page',
    exclude: ['page/test'],
    template: 'view/layout.html',
    loader: {
      client: 'framework/vue/entry/client-loader.js',
      server: 'framework/vue/entry/server-loader.js'
    }
  }
  alias: {
   asset: 'asset',
   component: 'component',
   framework: 'framework',
   store: 'store'
  },
  cdn,
  done(){ // 编译完成
   // 这里可以做你想做的事情哟,比如 打包上传 CDN
   if(cdn && cdn.url){
   }
  }
}

9.2.2 结合 Vue vue-server-renderer 做服务端渲染, 核心代码如下:

const renderer = require('vue-server-renderer');
// filepath 为 Webpack 构建的服务端代码
const bundleRenderer = renderer.createBundleRenderer(filepath, renderOptions);
// data 为 Node端获取到的数据
const context = { state: data };
return new Promise((resolve, reject) => {
  bundleRenderer.renderToString(context, (err, html) => {
  if (err) {
    reject(err);
  } else {
    resolve(html);
  }
});

请参考 egg-view-vue 和 egg-view-vue-ssr 实现: egg-view-vueegg-view-vue-ssr

拿到服务端渲染的 html 后,可以根据 manifest 资源依赖注入 css,js 等依赖,实际项目这里要考虑缓存。完整的基于 koa, express 项目请参考下面要介绍的 Egg + Vue 服务端渲染实现。


9.2.3 结合 React react-dom/server 做服务端渲染, 核心代码如下:

const React = require('react');
const ReactDOMServer = require('react-dom/server');
const reactClass = require(name);
return ReactDOMServer.renderToString(React.createElement(reactClass, locals))请参考 egg-view-react  egg-view-react-ssr 实现 egg-view-react  egg-view-react-ssr

拿到服务端渲染的 html 后,可以根据 manifest 资源依赖注入 css,js 等依赖,实际项目这里要考虑缓存。完整的基于 koa, express 项目请参考下面要介绍的 Egg + React 服务端渲染实现。


9.3 Egg + Vue 服务端渲染(Server Side Render)配置


如果要基于上面的要求实现一个 Egg + Vue 服务端渲染的构建配置, 我们服务端渲染的配置基础上面增加 egg: true 配置即可。


9.3.1 完整配置结构如下:

const BUILD_ENV = process.env.BUILD_ENV;
const cdn = BUILD_ENV === 'prod' ? { url: 'http://your.cdn.com'} : '',

module.exports = {
  egg: true,
  framework: 'vue', // 支持 react/weex
  entry: {
   include: 'page',
   exclude: ['page/test'],
   template: 'view/layout.html',
   loader: {
     client: 'framework/vue/entry/client-loader.js',
     server: 'framework/vue/entry/server-loader.js'
   }
  }
  alias: {
	asset: 'asset',
	component: 'component',
	framework: 'framework',
	store: 'store'
  },
  cdn,
  done(){ // 编译完成
	// 这里可以做你想做的事情哟,比如 打包上传 CDN
	if(cdn && cdn.url){
	}
  }
}

9.3.2 安装相关配置插件

项目骨架: egg-vue-webpack-boilerplate


9.4 Egg + React 服务端渲染(Server Side Render)配置

如果要基于上面的要求实现一个 Egg + React 服务端渲染的构建配置,我们服务端渲染的配置基础上面增加 egg: true即可。

完整结构如下:

const BUILD_ENV = process.env.BUILD_ENV;
const cdn = BUILD_ENV === 'prod' ? { url: 'http://your.cdn.com'} : '',
module.exports = {
  egg: true,
  framework: 'react', // 支持 react/weex
  entry: {
    include: 'page',
    exclude: ['page/test'],
    template: 'view/layout.html',
    loader: {
	client: 'framework/vue/entry/client-loader.js',
	server: 'framework/vue/entry/server-loader.js'
    }
  },
  alias: {
    asset: 'asset',
    component: 'component',
    framework: 'framework',
    store: 'store'
  },
  cdn,
  done(){ // 编译完成
   // 这里可以做你想做的事情哟,比如 打包上传 CDN
   if(cdn && cdn.url){
   }
  }
}

骨架项目请见: egg-react-webpack-boilerplate

9.5 Weex Native 和 Web 双端模式构建

假如要实现基于 Weex + Vue 构建配置项目该如何配置 webpack.config.js. 除了上面的要求外,还需要支持 Native 和 Web 构建。 基于 easywbpack-weex 配置也非常简单, 只需要 把 framework 配置为 weex 即可。

  • 首先安装 easywbpack-weex
npm i easywbpack-weex --save-dev
  • webpack.config.js 配置如下:
module.exports = {
  egg: true,
  framework: 'weex',
  entry: {
	include: 'page',
	exclude: ['page/test'],
	template: 'view/layout.html'
  }	
  alias: {
	asset: 'asset',
	component: 'component',
	framework: 'framework',
	store: 'store'
  },
  done(){ // 编译完成
   // 这里可以做你想做的事情哟
  }
}
  • 获取 Webpack Config 配置
const EasyWebpack = require('easywebpack-weex');
const webpackConfigList = EasyWebpack.getWebpackConfig()
  • 开发运行
easy server dev
easy server test
easy server prod
  • 编译
easy build dev
easy build test
easy build prod

骨架项目请见: easywebpack-weex-boilerplate


文档: hubcarl.github.io/easyw

编辑于 2017-10-28

文章被以下专栏收录