概览

  1. vue 和 js 改为webpack打包

    • 升级babel
    • 引入webpack config
    • 添加webpack loader
    • 生成入口列表 entry
    • 结合到gulp流程
    • 增加可视化打包分析
  2. 开发模式热更新

    • webpack-dev-server
  3. vue 拆分多入口 实现按需加载 按需引用vue-component

    • vue 文件入口 为指定入口
    • vue-web
    • vue-mobile
    • vue-article
    • vue-home
    • vue-stock
  4. vue 公共资源抽离vue-common

    • 增加splitChunks
    • chunks 设置 只抽取vue部分
  5. js 引用方式修改为 从vue引入

    • 减少体积
    • 不用手动控制js加载
  6. 拆分vue路由

  7. lodash 按需引用 去除全局lodash

    • 减小体积
    • 避免冲突
  8. js资源 preload

    • 加快速度
  9. vue warning 解决

    • 之前的打包方式 不会有warning提示
  10. confirm modal 拆分(暂缓)
    • 新增的考虑拆分
  11. poliyfill
    • 单独引用 30k

背景

snowman项目采用pug渲染 + vue渲染结合,vue渲染部分js资源未按需加载,页面加载全部的js

一. vue 和 js 改为webpack打包

1.升级babel

1
2
3
4
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.4",
}

.babelrc 改用preset-env

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
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": [
"Android >= 4.0",
"ios >= 9",
"ie 9"
]
}
}
]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-runtime",
["import", {
"libraryName": "lodash",
"libraryDirectory": "",
"camel2DashComponentName": false,
}]
]
}

node入口babel升级

1
2
3
4
require('@babel/register')({
presets: ['@babel/preset-env']
});
require('@babel/polyfill');

2.引入webpack config

webpack.common.js

webpack.dev.js

webpack.prod.js

entry 打包入口

1
2
'vue-web': './src/vue/web.js',
'vue-mobile': './src/vue/mobile.js',

3.添加webpack loader

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
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
// this applies to <template lang="pug"> in Vue components
test: /\.pug$/,
oneOf: [
// 这条规则应用到 Vue 组件内的 `<template lang="pug">`
{
resourceQuery: /^\?vue/,
use: ['pug-plain-loader']
},
// 这条规则应用到 JavaScript 内的 pug 导入
{
use: ['pug-loader']
}
]
},
// {
// test: /\.(css|less)$/,
// use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
// },
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
// include: /src/,
loader: 'babel-loader?cacheDirectory=true'
}
]
},

4.增加可视化打包分析

1
2
3
4
5
6
if (process.env.VISU) {
common.plugins.push(new BundleAnalyzerPlugin({
analyzerHost: 'localhost',
analyzerPort: '8080'
}));
}

使用npm run visu 即可浏览

分析最开始的vue-web.js 为500k

发现是snbchart和moment-timezone 错误引用造成的

查找问题发现

取消错误引用后,减小到350k

5.生成入口列表 js entry

目前只有vue文件是webpack 打包的,snowman是多入口 还有很多js文件也要分别打包处理

遍历src/js 获得webpack 入口列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const getEntrys = (path) => {
const entry = {};
var files = glob.sync(path);
files.forEach(item => {
// 获取js名称
const name = item.match(/.*\/(.*?).js$/)[1];
if (name) {
entry[name] = item;
}
});
return entry;
};

const jsEntry = getEntrys('./src/js/*.js');
1
2
3
4
5
6
const entry = {
'vue-web': './src/vue/web.js',
'vue-mobile': './src/vue/mobile.js',
...jsEntry,
'im': './src/js/im/index.js'
};

至此webpack打包已经能生成各种js文件,但是还不能运行。所以需要结合到gulp流程

6.结合到gulp流程

我们的pug文件热更新需要沿用gulp流程

webpack output至 gulp dist/js 目录

生成的文件注意不要加hash, gulp有统一处理

1
2
3
4
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist/js'),
},

增加webpack gulp命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import webpack from 'webpack';
// 载入webpack.config.js文件
import productConfig from '../webpack.prod';
import devConfig from '../webpack.dev';

const isDev = process.env.NODE_ENV === 'development';

export default function () {
return new Promise((resolve) => {
webpack(isDev ? devConfig : productConfig, function (err, stats) {
if (err) {
console.err(err);
} else {
resolve();
}
});
});
}

修改gulpfile

二. 开发模式热更新

采用 webpack-dev-server

启动在5001端口

为了与gulp dev 模式兼容, writeToDist 为true ,同样写到gulp 目录

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
const merge = require('webpack-merge');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const common = require('./webpack.common.js');

module.exports = merge(common, {
mode: 'development',
// devtool: 'inline-source-map',
devServer: {
contentBase: './dist/js',
hot: true,
writeToDisk: true,
headers: {
'Access-Control-Allow-Origin': '*'
},
host: '0.0.0.0',
port: 5001
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development')
}),
new CopyWebpackPlugin([{ from: './src/js/lib', to: './' }])
]
});

output publicPath 设置为0.0.0.0:5001 , 目的是为了热更新的js代码能被正确找到

1
2
3
4
5
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist/js'),
publicPath: 'http://0.0.0.0:5001/'
},

npm script

1
"dev": "npm run gulp-dev & npm run webpack-dev-server",

编译界面

三. vue 拆分多入口 实现按需加载

目前都在一块,没有分页面分路由拆分 所有vue页面都有全部的vue-web.js

原因在于只写了一个vue入口 src/vue/web.js 所有的vue组件都在这里引用,其实应该拆开

看注释 能看到大概的组件属于哪一页

计划先按照主要页面 拆分

  • 文章页
  • 个股页
  • 已登录首页
  • 未登录首页
  • 编辑器页

抽取通用的vue.common.js 包含一些所有页面都公用的配置和组件

1
2
3
4
5
6
7
8
9
10
11
const entry = {
'vue-web': './src/vue/web.js',
'vue-mobile': './src/vue/mobile.js',
'vue-article': './src/vue/article.js',
'vue-stock': './src/vue/stock.js',
'vue-home': './src/vue/home.js',
'vue-home-unsign': './src/vue/home-unsign.js',
'vue-write': './src/vue/write.js',
...jsEntry,
'im': './src/js/im/index.js'
};

后续考虑 使用dll将通用的node_modules 打包到一块, 加快打包速度,减少发版后用户端的js更新量。

文章页

pug文件中引用

个股页

pug文件中应用

已登录首页

未登录首页

四. 抽离公共vue-common

每个js有很多重复的部分

vue 公共资源抽离vue-common

增加splitChunks

https://webpack.js.org/plugins/split-chunks-plugin/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
optimization: {
splitChunks: {
// 只抽取vue开头的公共部分 为了向下兼容 不抽取 vue-web vue-mobile
chunks (chunk) {
return chunk.name.match(/^vue-(?!(mobile|web))/);
},
// 取消自动抽取
minSize: 3000000,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
default: {
minSize: 0,
minChunks: 4,
priority: -10,
name: 'vue-common'
}
}
}
}

五. js引用方式改为从vue引入

之前的引用方式

  • 文章页

  • 个股页

之前为了按需加载写的代码 可以不用单独在pug入口文件中写了,直接在 vue/article.js 或者 vue/stock.js 中 引用即可

改变引用方式后的打包图,可以发现 vue-stock 包含了 snbchart 并且 体积小于 之前stock_new.js的体积

六. 拆分vue路由

看下面这个打包图,vue-common中包含了所有的路由,并且发现有个bussiness.js 的模块挺大的,后来发现这个模块只有个股页用到了,所以应该打包到vue-stock 这个文件里

分析后发现是vue路由没有拆开的原因

vue/router/index

旧代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let routes;
const pathname = window.location.pathname.replace(/^\/snowman/, '');
if (~'/'.indexOf(pathname)) {
if (!window.SNOWMAN_LOGIN) {
routes = require('./home');
}
} else if (/^\/today/.test(pathname)) {
routes = require('./home');
} else if (/^\/u\/\d+\/?$/.test(pathname)) {
routes = require('./profiles');
} else if (/^\/k\/?$/.test(pathname)) {
routes = require('./search');
} else if (/^\/u\/[A-z0-9_]{4,}\/?$/.test(pathname)) {
routes = require('./profiles');
} else if (/^\/center\//.test(pathname)) {
routes = require('./center');
} else if (/^\/[sS]\/([^\/]+)\/detail/.test(pathname)) {
routes = require('./stock-info');
}
export default routes;

看这种写法是想拆分路由的 只是因为入口一样 无法拆分

现在有了不同的入口 就可以拆分了

改为

只有个股页 才引用 stock-info

匿名首页 只引用 home

个人页 只引用 profiles

个人中心 只引用 center

搜索 只引用 search

效果如下

vue-common 252 -> 200

可以看到 router 从 vue-common 中消失了

commponent 大小也减小,拆分到了 需要的js 中 实现按需加载

common 中的 business 也正确的只打包在 vue-stock 中

匿名首页 拆分 vue 路由后

七. lodash按需引用 去除全局lodash

lodash 多个地方重复引用,并且是全量引用

增加lodash tree shake

https://www.azavea.com/blog/2019/03/07/lessons-on-tree-shaking-lodash/

import foo from 'lodash/foo'

以这种方式引用才可以tree shake

从26k降到了6k

使用 babel-plugin-import

但是每次都手动写,不太方便。

分析原理 import { debounce } from 'lodash'这种引用方式不能按需加载的原因为:

其被babel转换为了

1
2
var lodash = require('lodash');
var debounce = lodash.debounce;

第一句 var lodash = require('lodash'); 就把所有的lodash都引进来了。

所以我们需要一个插件,能正确的babel转换

babel-plugin-import 就是提供了这么一个功能。

能直接转换为

1
var debounce = require('lodash/debounce');

所以最后我们采用 babel-plugin-import。并增加了lodash的引用路径配置。

采用import { debounce } from 'lodash'这种写法即可实现按需加载。

并且引用其他库时,也可以配置按需引用了,比如我们的 snb-lib-jsbridge等。

八. 资源preload

js资源加载时间很靠后

增加link preload

九. 目前资源加载情况

匿名首页

文章页

个股页

已登录首页

包分析