终极蛇皮上帝视角之微信小程序之告别,配置分析

终极蛇皮上帝视角之微信小程序之告别“刀耕火种”

2018/08/22 · 基础技术 · webpack, 小程序

原文出处: BuptStEve   

开门见山地说,小程序在日常开发中使用原生框架来开发还是挺不方便的,比如:

  • 不支持 npm
  • 不支持各种 CSS 预编译器
  • 不支持配置 Babel 来转换一些 JavaScript 新特性

这样一来和日常开发前端页面的体验相比来说,简直就像在刀耕火种

那么为了解决这些问题,我们能不能将前端开发中常用的 webpack 移植到小程序开发中呢?

当然可以!

图片 1

原文 

目录结构:

0.源码地址

  • 在 webpack-simple 中文件结构和小程序相似。
  • 而在 webpack-vue 中还增加了 vue-loader,因此你甚至还能利用 .vue 文件编写单文件组件。

注:已封装到 https://tuateam.github.io/tua… 中…

图片 2

版本号

├── README.md

1.文件结构

既然用 webpack 来编译源代码,那么很自然的我们的文件结构首先要分为 src/dist/,开发者工具的目标应该是 dist/ 目录。

注:开发者工具打开的应该是根目录,这样可以保存各种设置,可以在 project.config.json 中配置 "miniprogramRoot": "./dist/",

vue-cli2.8.1 (终端通过vue -V可查看)

├── build

1.1.src/ 中文件结构大概长这样:

. ├── app │ ├── app.js │ ├── app.json │ └── app.scss ├── assets │ └── vue-logo.png ├── comps │ └── todo │ ├── todo.js │ ├── todo.json │ ├── todo.less │ └── todo.wxml ├── pages │ └── index │ ├── index.js │ ├── index.json │ ├── index.less │ └── index.wxml ├── scripts │ ├── const │ │ ├── README.md │ │ └── index.js │ └── utils │ ├── README.md │ ├── event.js │ ├── format.js │ ├── index.js │ └── log.js ├── styles │ ├── global.styl │ ├── todomvc-app-css.css │ └── todomvc-common-base.css └── templates └── info.wxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.
├── app
│   ├── app.js
│   ├── app.json
│   └── app.scss
├── assets
│   └── vue-logo.png
├── comps
│   └── todo
│       ├── todo.js
│       ├── todo.json
│       ├── todo.less
│       └── todo.wxml
├── pages
│   └── index
│       ├── index.js
│       ├── index.json
│       ├── index.less
│       └── index.wxml
├── scripts
│   ├── const
│   │   ├── README.md
│   │   └── index.js
│   └── utils
│       ├── README.md
│       ├── event.js
│       ├── format.js
│       ├── index.js
│       └── log.js
├── styles
│   ├── global.styl
│   ├── todomvc-app-css.css
│   └── todomvc-common-base.css
└── templates
    └── info.wxml
  • app/: 应用入口
  • assets/: 资源文件,比如图片
  • comps/: 组件
  • pages/: 页面
  • scripts: 公用代码
  • scripts/const: 常量(已配置别名 @const)
  • scripts/utils: 辅助函数(已配置别名 @utils)
  • styles/: 公用样式
  • templates/: 模板

vue2.2.2

│ ├── build.js

1.2.dist/ 中文件结构大概长这样:

. ├── app.js ├── app.js.map ├── app.json ├── app.wxss ├── assets │ └── vue-logo.png ├── chunks │ ├── runtime.js │ ├── runtime.js.map │ ├── scripts.js │ ├── scripts.js.map │ ├── vendors.js │ └── vendors.js.map ├── comps │ └── todo │ ├── todo.js │ ├── todo.js.map │ ├── todo.json │ ├── todo.wxml │ └── todo.wxss ├── pages │ └── index │ ├── index.js │ ├── index.js.map │ ├── index.json │ ├── index.wxml │ └── index.wxss └── templates └── info.wxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.
├── app.js
├── app.js.map
├── app.json
├── app.wxss
├── assets
│   └── vue-logo.png
├── chunks
│   ├── runtime.js
│   ├── runtime.js.map
│   ├── scripts.js
│   ├── scripts.js.map
│   ├── vendors.js
│   └── vendors.js.map
├── comps
│   └── todo
│       ├── todo.js
│       ├── todo.js.map
│       ├── todo.json
│       ├── todo.wxml
│       └── todo.wxss
├── pages
│   └── index
│       ├── index.js
│       ├── index.js.map
│       ├── index.json
│       ├── index.wxml
│       └── index.wxss
└── templates
    └── info.wxml
  • chunks/: 公共依赖
    • runtime: 是 webapck 在运行时连接各个模块的代码
    • vendors: 是提取的 node_modules 下的依赖
    • scripts: 是提取的 src/scripts/ 下的依赖

webpack2.2.1

│ ├── check-versions.js

1.3.整个项目文件结构大概长这样:

. ├── README.md ├── dist/ ├── package.json ├── project.config.json ├── src/ ├── webpack.config.babel.js └── yarn.lock

1
2
3
4
5
6
7
8
.
├── README.md
├── dist/
├── package.json
├── project.config.json
├── src/
├── webpack.config.babel.js
└── yarn.lock
  • src/: 源码
  • dist/: 打包后代码

目录结构

│ ├── dev-client.js

2.webpack 基础配置

├── README.md

│ ├── dev-server.js

2.1.entry/output

小程序场景下的配置应该是多入口,主要分为 apppagescomps 这三类。

  • app: 将 src/app/ 下的文件编译成 dist/ 根目录下的 app.js/app.json/app.wxss
  • pages: src/pages/ -> dist/pages/
  • comps: src/comps/ -> dist/comps/

在输出 output 部分有个坑:因为小程序使用的是 global,所以必须添加配置 output.globalObjectglobal

不然…

JavaScript

thirdScriptError VM937:1 sdk uncaught third Error Cannot read property 'webpackJsonp' of undefined TypeError: Cannot read property 'webpackJsonp' of undefined at at at require () at at at require () at // runtime var a = window.webpackJsonp = window.webpackJsonp || []

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
thirdScriptError VM937:1
sdk uncaught third Error
Cannot read property 'webpackJsonp' of undefined
TypeError: Cannot read property 'webpackJsonp' of undefined
    at http://127.0.0.1:40247/appservice/chunks/runtime.js:34:51
    at http://127.0.0.1:40247/appservice/chunks/runtime.js:38:2
    at require (http://127.0.0.1:40247/appservice/__dev__/WAService.js:19:7859)
    at http://127.0.0.1:40247/appservice/__dev__/WAService.js:19:7573
    at http://127.0.0.1:40247/appservice/app.js:3:1
    at require (http://127.0.0.1:40247/appservice/__dev__/WAService.js:19:7859)
    at http://127.0.0.1:40247/appservice/appservice?t=1527755092895:1020:9
 
 
// runtime
var a = window.webpackJsonp = window.webpackJsonp || []

详情可参阅这个 pr

ps 在 mpvue 中似乎是通过修改 target 实现的… http://mpvue.com/build/mpvue-…

├── build

│ ├── utils.js

2.2.CommonChunk

在 webpack 4 中有一个 breaking change,即使用 SplitChunksPlugin 替换了之前很常用的 CommonsChunkPlugin

主要提取了三部分的公共代码:

  • runtime: 是 webapck 在运行时连接各个模块的代码
  • vendors: 是提取的 node_modules 下的依赖
  • scripts: 是提取的 src/scripts/ 下的依赖

现在又碰到个新的问题:如何引入这些 chunks

在前端项目中一般我们通过 HtmlWebpackPlugin 插件在 html 文件中添加 <script> 标签引入,然鹅小程序中并没有 html 文件…

计将安出?

总不能每次都手动去 dist/app.js 中 require 这些文件吧?

这时候就要介绍另一款插件了~:BannerPlugin

这个插件本来是用在文件头部添加 banner 的,但是也支持插入代码,因此利用这款插件我们就可以将这些公共依赖在 app.js 中统一引入一次即可。

TODO: 现版本的小程序提供了分包加载能力,因此这里还有优化空间

│  ├── build.js

│ ├── webpack.base.conf.js

2.3.CopyWebpackPlugin

顾名思义,这款插件的用处就是拷贝,利用这款插件我们就可以实现:

  • 复制 *.json
  • 复制 *.wxml
  • 复制 *.wxss
  • 复制 assets/
  • 复制 templates/

在使用时有一个知识点可以减少代码量:即 context 选项,这样就不用写 n 个 src/了…

JavaScript

new CopyWebpackPlugin(copyCfgArr, { context: resolve('src'), }),

1
2
3
new CopyWebpackPlugin(copyCfgArr, {
    context: resolve('src'),
}),

│  ├── check-versions.js

│ ├── webpack.dev.conf.js

2.4.预处理器和 CSS 的处理

这部分其实都是常规操作和一般 web 开发没啥区别,配置好对应的 loader 即可。

需要注意的点就是一定要使用 ExtractTextWebpackPlugin 插件来生成 .wxss 文件。

JavaScript

new ExtractTextPlugin('[name].wxss')

1
new ExtractTextPlugin('[name].wxss')

注:已换成 mini-css-extract-plugin

│  ├── dev-client.js

│ └── webpack.prod.conf.js

3.webpack + vue-loader

这部分谈谈如何利用 vue-loader 实现在小程序中引用单文件组件(.vue)。

先看看 src/ 下的文件结构:

. ├── app │ ├── App.vue │ ├── app.js │ └── app.json ├── assets │ └── vue-logo.png ├── comps │ ├── Filter │ │ ├── Filter.vue │ │ └── index.js │ └── Todo │ ├── Todo.vue │ └── index.js ├── pages │ ├── index │ │ ├── Index.vue │ │ └── index.js │ └── todos │ ├── Todos.vue │ └── index.js ├── scripts │ ├── const │ │ ├── README.md │ │ └── index.js │ └── utils │ ├── README.md │ ├── event.js │ ├── format.js │ ├── index.js │ └── log.js ├── styles │ ├── global.styl │ ├── todomvc-app-css.css │ └── todomvc-common-base.css └── templates └── info.wxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
.
├── app
│   ├── App.vue
│   ├── app.js
│   └── app.json
├── assets
│   └── vue-logo.png
├── comps
│   ├── Filter
│   │   ├── Filter.vue
│   │   └── index.js
│   └── Todo
│       ├── Todo.vue
│       └── index.js
├── pages
│   ├── index
│   │   ├── Index.vue
│   │   └── index.js
│   └── todos
│       ├── Todos.vue
│       └── index.js
├── scripts
│   ├── const
│   │   ├── README.md
│   │   └── index.js
│   └── utils
│       ├── README.md
│       ├── event.js
│       ├── format.js
│       ├── index.js
│       └── log.js
├── styles
│   ├── global.styl
│   ├── todomvc-app-css.css
│   └── todomvc-common-base.css
└── templates
    └── info.wxml

其实已经和一般的 web 项目很相似了~

│  ├── dev-server.js

├── config

3.1.vue-loader v15?

随着 webpack 升级到了 v4,官方与之配合的 vue-loader 也升级到了 v15。

现在 Vue Loader 15 使用了一个不一样的策略来推导语言块使用的 loader。

在 v15 中,<style lang="less"> 会完成把它当作一个真实的 *.less 文件来加载。因此,为了这样处理它,你需要在你的主 webpack 配置中显式地提供一条规则。

简单来说就是咱们之前配置过的各个预处理器规则会被 vue-loader 自动使用。

因此我们只需要简单地添加一条规则即可读取 .vue 文件:

JavaScript

{ test: /.vue$/, exclude: /node_modules/, loader: 'vue-loader', options: { compiler: { // mock vue-template-compiler compile: () => ({ staticRenderFns: [], }) }, }, },

1
2
3
4
5
6
7
8
9
10
11
12
13
{
    test: /.vue$/,
    exclude: /node_modules/,
    loader: 'vue-loader',
    options: {
        compiler: {
            // mock vue-template-compiler
            compile: () => ({
                staticRenderFns: [],
            })
        },
    },
},

options.compiler 是啥?

注意:随着 vue-loader 的升级,这部分的 mock 有变化…

JavaScript

options: { // mock vue-template-compiler compile: () => ({ staticRenderFns: [], }), parseComponent: require('vue-template-compiler') .parseComponent, }, },

1
2
3
4
5
6
7
8
9
options: {
    // mock vue-template-compiler
    compile: () => ({
        staticRenderFns: [],
    }),
    parseComponent: require('vue-template-compiler')
        .parseComponent,
    },
},

│  ├── utils.js

│ ├── dev.env.js

3.2.options.compiler

options.compiler 覆写用来编译单文件组件中 <template> 块的默认编译器。

在实际使用单文件组件时,我们通过 <template lang="wxml"> 来包裹原本的 .wxml 文件中的内容。

因为最终要编译成 .wxml 文件才能被开发者工具识别,所以我们还编写了一条规则通过 file-loader 生成最终的 .wxml文件:

JavaScript

{ // 处理 <template lang="wxml">{...}</template> // 生成 .wxml 文件 test: /.wxml$/, use: { loader: 'file-loader', options: { name: getNameByFilePathAndExt('.wxml'), }, }, },

1
2
3
4
5
6
7
8
9
10
11
{
    // 处理 <template lang="wxml">{...}</template>
    // 生成 .wxml 文件
    test: /.wxml$/,
    use: {
        loader: 'file-loader',
        options: {
            name: getNameByFilePathAndExt('.wxml'),
        },
    },
},

但是因为 vue-loader 默认会编译 template 中的内容将其生成一个个 render 函数。但其实在小程序场景中我们并不需要这一步骤。我们只想安安静静地将这些代码通过 file-loader 生成 .wxml 文件…

幸好 vue-loader 还提供了 options.compiler 这个参数用来传递自己的编译器。所以这里其实是 mock 了一下 vue-template-compiler

│  ├── vue-loader.conf.js

│ ├── index.js

3.3.Custom Blocks

最后还有个问题没有解决:如何处理 .json 文件?

在其他的小程序框架中是这样处理的:

  • wepy 中将其作为组件的 config 属性
JavaScript

export default class Index extends wepy.page { //页面配置 config = {
"navigationBarTitleText": "test" }; // ... }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f638bb8934473978340-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8934473978340-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8934473978340-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8934473978340-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8934473978340-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8934473978340-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8934473978340-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8934473978340-8">
8
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f638bb8934473978340-1" class="crayon-line">
export default class Index extends wepy.page {
</div>
<div id="crayon-5b8f638bb8934473978340-2" class="crayon-line crayon-striped-line">
    //页面配置
</div>
<div id="crayon-5b8f638bb8934473978340-3" class="crayon-line">
    config = {
</div>
<div id="crayon-5b8f638bb8934473978340-4" class="crayon-line crayon-striped-line">
        &quot;navigationBarTitleText&quot;: &quot;test&quot;
</div>
<div id="crayon-5b8f638bb8934473978340-5" class="crayon-line">
    };
</div>
<div id="crayon-5b8f638bb8934473978340-6" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f638bb8934473978340-7" class="crayon-line">
    // ...
</div>
<div id="crayon-5b8f638bb8934473978340-8" class="crayon-line crayon-striped-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>
  • mpvue 中是写在 main.js 的输出部分
JavaScript

// main.js export default { // 这个字段走 app.json config: { //
页面前带有 ^ 符号的,会被编译成首页,其他页面可以选填,我们会自动把
webpack entry 里面的入口页面加进去 pages: ['pages/logs/main',
'^pages/index/main'], window: { backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff', navigationBarTitleText:
'WeChat', navigationBarTextStyle: 'black' } } } //
src/pages/logs/main.js export default { config: {
navigationBarTitleText: '查看启动日志' } }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f638bb8937356076262-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8937356076262-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8937356076262-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8937356076262-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8937356076262-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8937356076262-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8937356076262-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8937356076262-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8937356076262-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8937356076262-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8937356076262-11">
11
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8937356076262-12">
12
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8937356076262-13">
13
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8937356076262-14">
14
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8937356076262-15">
15
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8937356076262-16">
16
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8937356076262-17">
17
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8937356076262-18">
18
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8937356076262-19">
19
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f638bb8937356076262-20">
20
</div>
<div class="crayon-num" data-line="crayon-5b8f638bb8937356076262-21">
21
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f638bb8937356076262-1" class="crayon-line">
// main.js
</div>
<div id="crayon-5b8f638bb8937356076262-2" class="crayon-line crayon-striped-line">
export default {
</div>
<div id="crayon-5b8f638bb8937356076262-3" class="crayon-line">
  // 这个字段走 app.json
</div>
<div id="crayon-5b8f638bb8937356076262-4" class="crayon-line crayon-striped-line">
  config: {
</div>
<div id="crayon-5b8f638bb8937356076262-5" class="crayon-line">
    // 页面前带有 ^ 符号的,会被编译成首页,其他页面可以选填,我们会自动把 webpack entry 里面的入口页面加进去
</div>
<div id="crayon-5b8f638bb8937356076262-6" class="crayon-line crayon-striped-line">
    pages: ['pages/logs/main', '^pages/index/main'],
</div>
<div id="crayon-5b8f638bb8937356076262-7" class="crayon-line">
    window: {
</div>
<div id="crayon-5b8f638bb8937356076262-8" class="crayon-line crayon-striped-line">
      backgroundTextStyle: 'light',
</div>
<div id="crayon-5b8f638bb8937356076262-9" class="crayon-line">
      navigationBarBackgroundColor: '#fff',
</div>
<div id="crayon-5b8f638bb8937356076262-10" class="crayon-line crayon-striped-line">
      navigationBarTitleText: 'WeChat',
</div>
<div id="crayon-5b8f638bb8937356076262-11" class="crayon-line">
      navigationBarTextStyle: 'black'
</div>
<div id="crayon-5b8f638bb8937356076262-12" class="crayon-line crayon-striped-line">
    }
</div>
<div id="crayon-5b8f638bb8937356076262-13" class="crayon-line">
  }
</div>
<div id="crayon-5b8f638bb8937356076262-14" class="crayon-line crayon-striped-line">
}
</div>
<div id="crayon-5b8f638bb8937356076262-15" class="crayon-line">
 
</div>
<div id="crayon-5b8f638bb8937356076262-16" class="crayon-line crayon-striped-line">
// src/pages/logs/main.js
</div>
<div id="crayon-5b8f638bb8937356076262-17" class="crayon-line">
export default {
</div>
<div id="crayon-5b8f638bb8937356076262-18" class="crayon-line crayon-striped-line">
  config: {
</div>
<div id="crayon-5b8f638bb8937356076262-19" class="crayon-line">
    navigationBarTitleText: '查看启动日志'
</div>
<div id="crayon-5b8f638bb8937356076262-20" class="crayon-line crayon-striped-line">
  }
</div>
<div id="crayon-5b8f638bb8937356076262-21" class="crayon-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

tua-mp 中目前采用的是自定义块的方式来实现的,即在 .vue 文件中新增了一个 <config> 块来编写配置。

<config> { "navigationBarTitleText": "查看启动日志" } </config> <template lang="wxml"> ... </template>

1
2
3
4
5
6
7
8
9
<config>
{
  "navigationBarTitleText": "查看启动日志"
}
</config>
 
<template lang="wxml">
    ...
</template>

但是并没有将 app.json 的内容放到 App.vue 中,因为有时需要读取这里的页面配置。如果写到 <config> `` 中的话,就无法读取了…

例如为了实现从分享后的页面后退返回首页这个功能,在辅助函数中就需要读取页面和 tabBar 配置,生成分享链接(实际分享地址是首页,然后从首页再导航到被分享的页面)。

因此最优解是页面配置写在 <config> ` 中,应用配置写在app.js` 的输出中。

TODO: 实现 mpvue 的方式处理 app.json

具体的配置如下:

JavaScript

{ // 处理 <config>{...}</config> 代码块 // 生成 .json 文件 resourceQuery: /blockType=config/, use: { loader: 'file-loader', options: { name: getNameByFilePathAndExt('.json'), }, }, },

1
2
3
4
5
6
7
8
9
10
11
{
    // 处理 <config>{...}</config> 代码块
    // 生成 .json 文件
    resourceQuery: /blockType=config/,
    use: {
        loader: 'file-loader',
        options: {
            name: getNameByFilePathAndExt('.json'),
        },
    },
},

│  ├── webpack.base.conf.js

│ └── prod.env.js

4.总结

综上,咱们在 webpack v4vue-loader v15 的帮助下,让小程序拥有了以下能力:

  • 加载 npm 包
  • 提取 CommonChunk 减少打包体积
  • babel 编译 JavaScript 代码
  • 支持 less/sass/stylus 等预处理器
  • 单文件组件

不过话又说回来了…

原生的小程序…又不是不能用~

图片 3

注:这句话是黄章说的,Teacher Luo 没说过这话哟~

以上 to be continued…

1 赞 收藏 评论

图片 4

│  ├── webpack.dev.conf.js

├── index.html

│  └── webpack.prod.conf.js

├── package.json

├── config

├── src

│  ├── dev.env.js

│ ├── App.vue

│  ├── index.js

│ ├── assets

│  └── prod.env.js

│ │ └── logo.png

├── index.html

│ ├── components

├── package.json

│ │ └── Hello.vue

├── src

│ └── main.js

│  ├── App.vue

└── static

│  ├── assets

入口文件:package.json

│  │  └── logo.png

"scripts": {

│  ├── components

"dev": "node build/dev-server.js",

│  │  └── Hello.vue

"build": "node build/build.js",

│  └── main.js

 "lint": "eslint --ext .js,.vue src" 

└── static

}

webpack配置

当我们执行 npm run dev / npm run build 时运行的的是 node build/dev-sev-server.js 或 node build/build.js

主要对build目录下的webpack配置做详细分析

dev-server.js

webpack.base.conf.js

  // 检查 Node 和 npm 版本

入口文件entry

require('./check-versions')()

entry: {

  // 获取 config/index.js 的默认配置

        app:'.src/main.js'

var config = require('../config')

}

  // 如果Node 的环境无法判断当前是dev/product 环境

输出文件output

  // 使用 config.dev.env.NODE_ENV 作为当前的环境

config的配置在config/index.js文件中

if(!process.env.NODE_ENV)process.env.NODE_ENV=JSON.parse(confi g.dev.env.NODE_ENV)

output: {

  // 使用nodeJS 自带的路径工具

output: {

var path = require('path')

path: config.build.assetsRoot,//导出目录的绝对路径

  // 使用 express

 filename:'[name].js',//导出文件的文件名

var express = require('express')

 publicPath: process.env.NODE_ENV ==='production'? config.build.assetsPublicPath : config.dev.assetsPublicPath//生产模式或开发模式下html、js等文件内部引用的公共路径

  //  使用webpack

}

var webpack = require('webpack')

文件解析resolve

  // 一个可以强制打卡浏览器并挑战到指定url 的插件

主要设置模块如何被解析。

var opn = require('opn')

resolve: {extensions: ['.js','.vue','.json'],//自动解析确定的拓展名,使导入模块时不带拓展名

  // 使用proxyTable

  alias: {

var proxyMiddleware = require('http-proxy-middleware')

// 创建import或require的别名

  // 使用 dev 环境的webpack 配置

'vue$':'vue/dist/vue.esm.js',

var webpackConfig = require('./webpack.dev.conf')

'@': resolve('src')

  // 如果没有指定运行端口,使用config.dev.port 作为运行端口

   }

var port = process.env.PORT || config.dev.port 

}

  // 使用config.dev.proxyTable 的配置作为proxyTable 的代理配置

模块解析module

var proxyTable = config.dev.proxyTable

如何处理项目不同类型的模块。

  // 使用 express 启动一个服务

module: {

var app = express()

rules: [  

  // 启动webpack 进行编译

  {

var compiler = webpack(webpackConfig)

     test:/.vue$/,// vue文件后缀 

  // 启动 webpack-dev-middleware, 将 编译后的文件暂存到内存中

     loader:'vue-loader',//使用vue-loader处理

var devMiddleware =require('webpack-dev-middleware')(compiler, { publicPath: webpackConfig.output.publicPath, stats: { colors: true, chunks: false } })

      options: vueLoaderConfig//options是对vue-loader做的额外选项配置   

  // 启动 webpack-hot-middleware, 也就是我们常说的Hot-reload

 },    

var hotMiddleware = require('webpack-hot-middleware')(compiler) compiler.plugin('compilation', function (compilation) { compilation.plugin('html-webpack-plugin-after-emit', function  (data, cb) { hotMiddleware.publish({ action: 'reload' }) cb() }) })

{

  // 将proxyTable 中的请求配置挂在启动的express 服务上

test:/.js$/,// js文件后缀      

Object.keys(proxyTable).forEach(function (context) { var options = proxyTable[context] if (typeof options === 'string') { options = { target: options } } app.use(proxyMiddleware(context, options)) })

loader:'babel-loader',//使用babel-loader处理 

  // 使用 connect-hisory-api-fallback 匹配资源,如果不匹配就可以重 定向到指定地址

  include: [resolve('src'), resolve('test')]//必须处理包含src和test文件夹    },    {test:/.(png|jpe?g|gif|svg)(?.*)?$/,//图片后缀      loader:'url-loader',//使用url-loader处理      query: {// query是对loader做额外的选项配置        limit:10000,//图片小于10000字节时以base64的方式引用        name: utils.assetsPath('img/[name].[hash:7].[ext]')//文件名为name.7位hash值.拓展名      }    },    {test:/.(woff2?|eot|ttf|otf)(?.*)?$/,//字体文件      loader:'url-loader',//使用url-loader处理      query: {limit:10000,//字体文件小于1000字节的时候处理方式        name: utils.assetsPath('fonts/[name].[hash:7].[ext]')//文件名为name.7位hash值.拓展名

app.use(require('connect-history-api-fallback')())

}

  // 将暂存到内存中的 wepack 编译后的文件挂载带express 服务上

}

app.use(devMiddleware)

]

  // 将Hot-reload 挂载到 express 服务上

}

app.use(hotMiddleware)

注: 关于query仅由于兼容性原因而存在。请使用options代替。

  //拼接到 static 文件夹的静态资源路径

webpack.dev.conf.js

var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)

开发环境下的webpack配置,通过merge方法合并webpack.base.conf.js基础配置

  // 为静态资源提供相应服务

var merge =require('webpack-merge')var baseWebpackConfig =require('./webpack.base.conf')module.exports = merge(baseWebpackConfig, {})

app.use(staticPath, express.static('./static'))

模块配置

  // 让我们这个express 服务监听port 的请求,并且将此服务作为 dev-server.js的接口暴露

module: {//通过传入一些配置来获取rules配置,此处传入了sourceMap: false,表示不生成sourceMap  rules: utils.styleLoaders({sourceMap: config.dev.cssSourceMap })

module.exports = app.listen(port, function (err) { if (err) { console.log(err) return } var uri = ':' + port console.log('Listening at ' + uri + 'n')

}

  // 如果不是测试环境,自动打开浏览器并跳到我们的开发地址

在util.styleLoaders中的配置如下

if (process.env.NODE_ENV !== 'testing') { opn(uri) }

exports.styleLoaders =function (options) {var output = []//定义返回的数组,数组中保存的是针对各类型的样式文件的处理方式var loaders = exports.cssLoaders(options)// 调用cssLoaders方法返回各类型的样式对象(css: loader)for (var extensionin loaders) {//循环遍历loadersvar loader = loaders[extension]//根据遍历获得的key(extension)来得到value(loader)    output.push({//      test:newRegExp('\.' + extension +'$'),// 处理的文件类型      use: loader//用loader来处理,loader来自loaders[extension]    })  }return output

Webpack.dev.conf.js

}

    // 同样的使用了config/index.js

上面的代码中调用了exports.cssLoaders(options),用来返回针对各类型的样式文件的处理方式,具体实现如下

var config = require('../config')

exports.cssLoaders =function (options) {  options = options || {}var cssLoader = {loader:'css-loader',options: {//options是loader的选项配置      minimize: process.env.NODE_ENV ==='production',//生成环境下压缩文件      sourceMap: options.sourceMap//根据参数是否生成sourceMap文件    }  }functiongenerateLoaders (loader, loaderOptions) {//生成loadervar loaders = [cssLoader]// 默认是css-loaderif (loader) {// 如果参数loader存在      loaders.push({loader: loader +'-loader',options:Object.assign({}, loaderOptions, {//将loaderOptions和sourceMap组成一个对象          sourceMap: options.sourceMap        })      })    }if (options.extract) {// 如果传入的options存在extract且为truereturn ExtractTextPlugin.extract({//ExtractTextPlugin分离js中引入的css文件        use: loaders,//处理的loader        fallback:'vue-style-loader'//没有被提取分离时使用的loader      })    }else {return ['vue-style-loader'].concat(loaders)    }  }return {//返回css类型对应的loader组成的对象 generateLoaders()来生成loader    css: generateLoaders(),postcss: generateLoaders(),less: generateLoaders('less'),sass: generateLoaders('sass', {indentedSyntax:true }),scss: generateLoaders('sass'),stylus: generateLoaders('stylus'),styl: generateLoaders('stylus')

    // 使用webpack

}

var webpack = require('webpack')

}

    //使用webpack 配置合并插件

插件配置

var merge = require('webpack-merge')

plugins: [new webpack.DefinePlugin({// 编译时配置的全局变量'process.env': config.dev.env//当前环境为开发环境  }),new webpack.HotModuleReplacementPlugin(),//热更新插件new webpack.NoEmitOnErrorPlugin(),//不触发错误,即编译后运行的包正常运行new HtmlWebpackPlugin({//自动生成html文件,比如编译后文件的引入    filename:'index.html',//生成的文件名    template:'index.html',//模板    inject:true  }),new FriendlyErrorsPlugin()//友好的错误提示

    // 使用一些小工具

]

var utils = require('./utils')

webpack.prod.conf.js

    // 加载webpack.base.conf

生产环境下的webpack配置,通过merge方法合并webpack.base.conf.js基础配置

var baseWebpackConfig = require('./webpack.base.conf')

module的处理,主要是针对css的处理

    // 使用 html-webpack-plugin 插件,这个插件可以帮助我们自动生成html,并注入 到.html 文件中

同样的此处调用了utils.styleLoaders

var HtmlWebpackPlugin = require('html-webpack-plugin')

module: {rules: utils.styleLoaders({sourceMap: config.build.productionSourceMap,extract:true

    // 将 Hol-reload 相对路径添加到webpack.base.conf 的 对应 entry 前

})

Object.keys(baseWebpackConfig.entry).forEach(function(name) {baseWebpackConfig.entry[name]=['./build/dev-client'].concat(baseWebpackConfi g.entry[name]) })

}

    //将我们 webpack.dev.conf.js 的配置和webpack.base.conf.js 的配置合并

输出文件output

module.exports = merge(baseWebpackConfig, { module: {

output: {//导出文件目录  path: config.build.assetsRoot,//导出的文件名  filename: utils.assetsPath('js/[name].[chunkhash].js'),//非入口文件的文件名,而又需要被打包出来的文件命名配置,如按需加载的模块  chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')

*    // 使用 styleLoaders* 

}

loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) },

插件plugins

    // 使用 #eval-source-mao 模式作为开发工具,

var path =require('path')var utils =require('./utils')var webpack =require('webpack')var config =require('../config')var merge =require('webpack-merge')var baseWebpackConfig =require('./webpack.base.conf')var CopyWebpackPlugin =require('copy-webpack-plugin')var HtmlWebpackPlugin =require('html-webpack-plugin')var ExtractTextPlugin =require('extract-text-webpack-plugin')var OptimizeCSSPlugin =require('optimize-css-assets-webpack-plugin')var env = config.build.envplugins: [new webpack.DefinePlugin({'process.env': env//配置全局环境为生产环境  }),new webpack.optimize.UglifyJsPlugin({//js文件压缩插件    compress: {//压缩配置      warnings:false// 不显示警告    },sourceMap:true//生成sourceMap文件  }),new ExtractTextPlugin({//将js中引入的css分离的插件    filename: utils.assetsPath('css/[name].[contenthash].css')//分离出的css文件名  }),//压缩提取出的css,并解决ExtractTextPlugin分离出的js重复问题(多个文件引入同一css文件)new OptimizeCSSPlugin(),//生成html的插件,引入css文件和js文件new HtmlWebpackPlugin({filename: config.build.index,//生成的html的文件名    template:'index.html',//依据的模板    inject:true,//注入的js文件将会被放在body标签中,当值为'head'时,将被放在head标签中    minify: {//压缩配置      removeComments:true,//删除html中的注释代码      collapseWhitespace:true,//删除html中的空白符      removeAttributeQuotes:true//删除html元素中属性的引号    },chunksSortMode:'dependency'//按dependency的顺序引入  }),//分离公共js到vendor中new webpack.optimize.CommonsChunkPlugin({name:'vendor',//文件名    minChunks: functions(module, count) {// 声明公共的模块来自node_modules文件夹return (module.resource &&/.js$/.test(module.resource) &&module,resource.indexOf(path.join(__dirname,'../node_modules')) ===0)    }  }),//上面虽然已经分离了第三方库,每次修改编译都会改变vendor的hash值,导致浏览器缓存失效。原因是vendor包含了webpack在打包过程中会产生一些运行时代码,运行时代码中实际上保存了打包后的文件名。当修改业务代码时,业务代码的js文件的hash值必然会改变。一旦改变必然会导致vendor变化。vendor变化会导致其hash值变化。//下面主要是将运行时代码提取到单独的manifest文件中,防止其影响vendor.jsnew webpack.optimize.CommonsChunkPlugin({name:'mainifest',chunks: ['vendor']  }),// 复制静态资源,将static文件内的内容复制到指定文件夹new CopyWebpackPlugin([{from: path.resolve(__dirname,'../static'),to: config.build.assetsSubDirectory,ignore: ['.*']//忽视.*文件

module.exports = merge(baseWebpackConfig, {

}])

 module: {

]

       // 使用 styleLoaders 

额外配置

  loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) },

if (config.build.productionGzip) {//配置文件开启了gzip压缩//引入压缩文件的组件,该插件会对生成的文件进行压缩,生成一个.gz文件var CompressionWebpackPlugin =require('compression-webpack-plugin')  webpackConfig.plugins.push(new CompressionWebpackPlugin({asset:'[path].gz[query]',//目标文件名      algorithm:'gzip',//使用gzip压缩      test:newRegExp(//满足正则表达式的文件会被压缩'\.(' +        config.build.productionGzipExtensions.join('|') +')$'      ),threshold:10240,//资源文件大于10240B=10kB时会被压缩      minRatio:0.8//最小压缩比达到0.8时才会被压缩

*      // eval-source-map is faster for development // 使用 #eval-source-map 模式作为开发工具,此配置可参考 DDFE 往期文章详细了解* 

})

  devtool: '#eval-source-map', plugins: [

)

*      // definePlugin 接收字符串插入到代码当中, 所以你需要的话可以写上 JS 的字符串* new webpack.DefinePlugin({ 'process.env': config.dev.env })

}

  new webpack.optimize.OccurenceOrderPlugin(),

npm run dev

     // HotModule 插件在页面进行变更的时候只会重回对应的页面模块,不会重绘整个 html 文件 

有了上面的配置之后,下面看看运行命令npm run dev发生了什么

  new webpack.HotModuleReplacementPlugin(),

在package.json文件中定义了dev运行的脚本

*    // 使用了 NoErrorsPlugin 后页面中的报错不会阻塞,但是会在编译结束后报错* 

"scripts": {

  new webpack.NoErrorsPlugin(),

"dev": "node build/dev-server.js",

*    // 将 index.html 作为入口,注入 html 代码后生成 index.html文件* 

"build": "node build/build.js"

  new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html', inject: true }) ] })

},

Webpack.base.conf.js

当运行npm run dev命令时,实际上会运行dev-server.js文件

 

该文件以express作为后端框架

    // 使用 nodejs 自带的文件路径

// nodejs环境配置var config =require('../config')if (!process.env.NODE_ENV) {  process.env.NODE_ENV =JSON.parse(config.dev.env.NODE_ENV)}var opn =require('opn')//强制打开浏览器var path =require('path')var express =require('express')var webpack =require('webpack')var proxyMiddleware =require('http-proxy-middleware')//使用代理的中间件var webpackConfig =require('./webpack.dev.conf')//webpack的配置var port = process.env.PORT || config.dev.port//端口号var autoOpenBrowser = !!config.dev.autoOpenBrowser//是否自动打开浏览器var proxyTable = config.dev.proxyTable//http的代理urlvar app = express()//启动expressvar compiler = webpack(webpackConfig)//webpack编译//webpack-dev-middleware的作用//1.将编译后的生成的静态文件放在内存中,所以在npm run dev后磁盘上不会生成文件//2.当文件改变时,会自动编译。//3.当在编译过程中请求某个资源时,webpack-dev-server不会让这个请求失败,而是会一直阻塞它,直到webpack编译完毕var devMiddleware =require('webpack-dev-middleware')(compiler, {publicPath: webpackConfig.output.publicPath,quiet:true})//webpack-hot-middleware的作用就是实现浏览器的无刷新更新var hotMiddleware =require('webpack-hot-middleware')(compiler, {log:() => {}})//声明hotMiddleware无刷新更新的时机:html-webpack-plugin 的template更改之后compiler.plugin('compilation',function (compilation) {  compilation.plugin('html-webpack-plugin-after-emit',function (data, cb) {    hotMiddleware.publish({action:'reload' })    cb()  })})//将代理请求的配置应用到express服务上Object.keys(proxyTable).forEach(function (context) {var options = proxyTable[context]if (typeof options ==='string') {    options = {target: options }  }  app.use(proxyMiddleware(options.filter || context, options))})//使用connect-history-api-fallback匹配资源//如果不匹配就可以重定向到指定地址app.use(require('connect-history-api-fallback')())// 应用devMiddleware中间件app.use(devMiddleware)// 应用hotMiddleware中间件app.use(hotMiddleware)// 配置express静态资源目录var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)app.use(staticPath, express.static('./static'))var uri =':' + port//编译成功后打印uridevMiddleware.waitUntilValid(function () {console.log('> Listening at ' + uri +'n')})//启动express服务module.exports = app.listen(port,function (err) {if (err) {console.log(err)return  }// 满足条件则自动打开浏览器if (autoOpenBrowser && process.env.NODE_ENV !=='testing') {

var path = require('path')

opn(uri)

    // 引入 config/index.js

}

var config = require('../config')

})

    // 引入一些小工具

npm run build

var utils = require('./utils')

由于package.json中的配置,运行此命令后会执行build.js文件

    // 拼接我们的工作区路径为一个绝对路径

process.env.NODE_ENV ='production'//设置当前环境为productionvar ora =require('ora')//终端显示的转轮loadingvar rm =require('rimraf')//node环境下rm -rf的命令库var path =require('path')//文件路径处理库var chalk =require('chalk')//终端显示带颜色的文字var webpack =require('webpack')var config =require('../config')var webpackConfig =require('./webpack.prod.conf')//生产环境下的webpack配置// 在终端显示ora库的loading效果var spinner = ora('building for production...')spinner.start()// 删除已编译文件rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {if (err)throw err//在删除完成的回调函数中开始编译  webpack(webpackConfig,function (err, stats) {    spinner.stop()//停止loadingif (err)throw err// 在编译完成的回调函数中,在终端输出编译的文件    process.stdout.write(stats.toString({colors:true,modules:false,children:false,chunks:false,chunkModules:false    }) +'nn')

var projectRoot = path.resolve(__dirname, '../')

})

    // 将nodeJs 作为我们的编译环境

})

var env = process.env.NODE_ENV

    // 将在dev 环境下开启cssSourceMap 在config/index.js 中可配置

var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)

    // 是否在production 环境下开启 ssSourceMap 在config/index.js 中可配置

var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)

    // 最终是否使用cssSourceMap

var useCssSourceMap = cssSourceMapDev || cssSourceMapProd

module.exports = {

  entry: {

      // 编译文件入口

    app: './src/main.js' 

      },

  output: {

      // 编译输出的根路径

       path: config.build.assetsRoot,

      // 正式发布环境下编译输出的发布路径

     publicPath:process.env.NODE_ENV==='production'? config.build.assetsPublicPath : config.dev.assetsPublicPath,

      // 编译输出的文件名

    filename: '[name].js' 

 },

 resolve: {

      // 自动补全的扩展名

     extensions: ['', '.js', '.vue'],

      // 不进行自动补全货处理的文件或者文件夹

  fallback: [path.join(__dirname, '../node_modules')],

    alias: {

        // 默认路径带了 例如 import vue for ‘vue’或自动到’vue/dist/vue.common.js’

      'vue': 'vue/dist/vue.common.js',

     'src': path.resolve(__dirname, '../src'),

     'assets':path.resolve(__dirname,'../src/assets'),

     'components': path.resolve(__dirname, '../src/components')

    }

    },

    resolveLoader: { fallback: [path.join(__dirname, '../node_modules')]

   },

  module: {

      preLoaders: [

*          // 预处理的文件及使用的 loader* 

        { test: /.vue$/, loader: 'eslint', include: projectRoot, exclude: /node_modules/ }, { test: /.js$/, loader: 'eslint', include: projectRoot, exclude: /node_modules/ } ], loaders: [

*          //  需要处理的文件及使用的loader*

        { test: /.vue$/, loader: 'vue' },

        { test: /.js$/, loader: 'babel', include: projectRoot, exclude: /node_modules/ }, { test: /.json$/, loader: 'json' },

        { test: /.(png|jpe?g|gif|svg)(?.*)?$/,

        loader:'url',

          query: {

             limit: 10000,

            name:utils.assetsPath('img/[name].[hash:7].[ext]')

          }

      },

     {

      test: /.(woff2?|eot|ttf|otf)(?.*)?$/,

      loader:'url',

      query:{limit:10000,

      name: utils.assetsPath('fonts/[name].[hash:7].[ext]')

    }

   }

  ]

},

eslint: {

*      // eslint 代码检查配置工具* 

    formatter: require('eslint-friendly-formatter')

  },

vue: {

*      //  .vue 文件配置 loader 及工具*

    loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }),

    postcss: [ require('autoprefixer')({ browsers: ['last 2 versions'})]

  }

}

config/index.js

 

    // 使用 nodejs 自带的文件路径

 

var path = require('path')

 

module.exports = {

 

  build: {

 

      // 使用 confi/prod.env.js 中定义的编译环境

 

    env: require('./prod.env'),

 

    index: path.resolve(__dirname, '../dist/index.html'),

 

      // 编译出的静态资源根路径

 

    assetsRoot: path.resolve(__dirname, '../dist'),

 

      // 编译输出的二级目录

 

    assetsSubDirectory: 'static',

 

      // 编译发布上线路径的根目录,可配置为资源服务器域名CDN 域名

 

    assetsPublicPath: '/',

 

      // 是否开启 cssSourceMap

 

    productionSourceMap: true,

 

      // 是否开启 gzip

 

    productionGzip: false,

 

      // 需要使用gzip 压缩的文件扩展名

 

    productionGzipExtensions: ['js', 'css']

 

  },

 

    // dev 环境

 

dev: {

 

    //  使用 config/dev.env.js 中定义的编译环境

 

  env: require('./dev.env'),

 

    // 运行测试网页的端口

 

 port: 8080,

 

    // 编译输出的二级目录

 

 assetsSubDirectory: 'static',

 

    //  编译发布上线的根目录,可以配置为资源服务器域名或CDN 域名

 

  assetsPublicPath: '/',

 

    // 需要 proxyTable 代理的接口

 

  proxyTable: {},

 

    // 是否需要开启 cssSourceMap

 

  cssSourceMap: false 

 

}}

Build.js

    // 检查node 和npm 版本

require('./check-versions')()

    // 使用了shelljs 插件,可以让我们在node 环境的js 中使用 shell

require('shelljs/global')

env.NODE_ENV = 'production'

    // 使用 nodejs 自带的文件路径

var path = require('path')

    // 加载config.js

var config = require('../config')

    // 一个很好看的loading 插件

var ora = require('ora')  

    // 加载 webpack

var webpack = require('webpack')

    // 加载 webpack.prod.conf

var webpackConfig = require('./webpack.prod.conf')

    // 输出提示信息~提示用户请在http 服务下查看页面,否则为空白页

console.log(

  ' Tip:n' +

  ' Built files are meant to be served over an HTTP server.n' +

  ' Opening index.html over file:// won't work.n' 

)

    // 使用 ora 打印出loading + log

var spinner = ora('building for production...')

    // 开始loading 动画

spinner.start()

    // 拼接编译输出文件路径

var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)

    //  删除这个文件(递归删除)

rm('-rf', assetsPath)

    //  创建此文件夹

mkdir('-p', assetsPath)

    // 复制 static 文件夹到我们的编译输出目录

cp('-R', 'static/*', assetsPath)

    // 开始 webpack 的编译

webpack(webpackConfig, function (err, stats) {

*    // 编译成功的回调函数* 

spinner.stop()

 if (err) throw err

process.stdout.write(stats.toString({

      colors: true,

    modules: false,

    children: false,

    chunks: false,

     chunkModules: false }) + 'n')

})

Webpack.prod.conf.js

 

      // 使用 nodejs 自带的文件路径

 

    var path = require('path')

 

      // 加载 confi.index.js

 

    var config = require('../config')

 

      // 使用一些工具

 

    var utils = require('./utils')

 

      // 加载 webpack

 

    var webpack = require('webpack')

 

      // 加载webpack 配置合并工具

 

    var merge = require('webpack-merge')

 

      // 加载 webpack.base.conf.js

 

    var baseWebpackConfig = require('./webpack.base.conf')

 

      // 一个webpack 扩展,可以提取一些代码并且将他们和文件分离开

 

      // 如果我们想将webpack 打包成一个文件 css js 分离开,那我们需要这个插件

 

    var ExtractTextPlugin = require('extract-text-webpack-plugin')

 

      //一个可以插入 html 并且创建新的 .html 文件的插件

 

    var HtmlWebpackPlugin = require('html-webpack-plugin')

 

    var env = config.build.env

 

     // 合并 webpack.base.conf.js

 

    var webpackConfig = merge(baseWebpackConfig, {

 

      module: {

 

          // 使用 loader

 

        loaders: utils.styleLoaders({

 

          sourceMap: config.build.productionSourceMap, extract: true })

 

       },

 

        // 是否使用 #source-map 开发工具

 

      devtool: config.build.productionSourceMap ? '#source-map' : false, output: {

 

        // 编译输出项目

 

      path: config.build.assetsRoot,

 

        // 编译输出文件名

 

        // 我们可以在hash 后加 :6 决定使用几位 hash 值

 

      filename: utils.assetsPath('js/[name].[chunkhash].js'),

 

        // 没有指定输出名的文件输出的文件名

 

      chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') },

 

        vue: {

 

          //  编译 .vue 文件的使用的loader

 

        loaders: utils.cssLoaders({

 

          sourceMap: config.build.productionSourceMap, extract: true }) }, plugins: [

 

            // 使用的插件  definePlugin 接收字符串插入到代码当中,所以你需要的话写上

 

          new webpack.DefinePlugin({ 'process.env': env }),

 

            // 压缩js (同样可以压缩css)

 

          new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }),

 

          new webpack.optimize.OccurrenceOrderPlugin(),

 

            // 将css 文件分离出来

 

          new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),

 

            // 输入 输出的 .html 文件

 

          new HtmlWebpackPlugin({ filename: config.build.index,template: 'index.html',

 

            // 是否注入 html

 

          inject: true,

 

    // 压缩的方式

 

      minify: {

 

          removeComments: true,

 

          collapseWhitespace: true,

 

          removeAttributeQuotes: true 

 

          chunksSortMode: 'dependency' }),

 

              // 没有指定输出文件名的文件输出的静态文件名

 

       new webpack.optimize.CommonsChunkPlugin({

 

          name: 'vendor',

 

          minChunks: function (module, count) {

 

      return(

 

        module.resource&&/.js$/.test(module.resource)&&

 

        module.resource.indexOf(

 

           path.join(__dirname, '../node_modules')

 

         ) === 0 ) }

 

      }),

 

      // 没有指定输出文件名称的文件输出的静态文静名

 

    new webpack.optimize.CommonsChunkPlugin({

 

        name: 'manifest',

 

        chunks: ['vendor'] })

 

       ]

 

     })

 

      // 开启 gzip 的情况下使用下方的配置

 

    if (config.build.productionGzip) {

 

*      // 加载 compression-webpack-plugin 插件* 

 

  var CompressionWebpackPlugin = require('compression-webpack-plugin')

 

*      // 向webpackconfig.plugins中加入下方的插件* 

 

  var reProductionGzipExtensions='\.('+ config.build.productionGzipExtensions.join('|') + '$)' 

 

    webpackConfig.plugins.push(

 

*        // 使用 compression-webpack-plugin 插件进行压缩* 

 

    new CompressionWebpackPlugin({

 

        asset: '[path].gz[query]',

 

         algorithm: 'gzip',

 

         test: new RegExp(reProductionGzipExtensions),

 

*        // 注:此处因有代码格式化的bug,与源码有差异*

 

         threshold: 10240, minRatio: 0.8 })

 

    ) }

 

    module.exports = webpackConfig

 本文参考  仅学习交流

本文由澳门威斯尼人平台登录发布于Web前端,转载请注明出处:终极蛇皮上帝视角之微信小程序之告别,配置分析

相关阅读