gmfe / Think

观麦前端团队的官方博客
68 stars 3 forks source link

xlsx模块引入优化 #22

Open HecateDK opened 6 years ago

HecateDK commented 6 years ago

xlsx模块引入优化

背景

station项目技术栈使用react+es6+react-router4+webpack+babel,项目业务模块多达数百个,项目依赖上千个,尽管使用了一系列webpack打包与性能优化的方案,如happypack、DllPlugin等,但生产环境全量构建仍需要三分钟左右。在优化xlsx模块前,运行npm run monitor可以看到,项目依赖总计20.44MB,一共有1498个模块,其中xlsx占了37.4%:

image

然而,我们只需要用到xlsx模块的xlsx.min.js文件,其他的都是累赘,并且大大拖慢了打包速度,所以不得不思考如何对xlsx模块进行优化。

webpack externals

xlsx模块那么大,首先想到的就是使用webpack externals。官方文档对于externals的定义是:

webpack可以不处理应用的某些依赖库,使用externals配置后,依旧可以在代码中通过CMD、AMD或者window/global全局的方式访问。

module.exports = {
  ...
  externals: {
    xlsx: "XLSX"
  },
  ...
}

可以看到项目依赖减少到14.26MB,并且webpack没有打包xlsx模块了。

image

因而只需要通过 script 标签引入 xlsx.min.js 即可。 但 xlsx.min.js 有接近 1M,而在具体的业务上,首页和大部分页面都不需要用到 xlsx,更合理的做法是动态引入 xlsx。

实现

通过与同类型的模块对比和思考,xlsx.min.js、pdfmake.min.js、BMapLib.min.js文件都非常大,webpack打包它们要耗费很长时间。可以运用script单独引入它们,但如果放在template上,则会拖慢首页加载速度:

image

如上图所示,我们提供了loadScript 方法用于动态插入 script 标签。针对那些较大的、不想打包到bundle上的js,但项目中使用的库所依赖的js文件,都通过 loadScript 方法进行动态加载。

再运用Dynamic imports,按需加载所用到的库,类似requireGmXlsx方法,按需加载gm-xlsx

function requireGmXlsx(callback) {
    asyncLoadJS('xlsx', () => {
        require.ensure([], function(require) {
            const res = require('gm-xlsx');
            callback(res);
        });
    });
}

webpack 在编译时,会静态地解析代码中的 reuqire.ensure ,同时将模块添加到一个分开的 chunk 当中。这个新的 chunk 会被 webpack 按需加载。 gm-xlsx需要依赖xlsx.min.js,所以asyncLoadJS用于异步加载所需的js文件:


const asyncLoadJS = function(conf, callback){
    const target = config[conf];

    if (!target) {
        console.error('模块不存在');
        return;
    }

    if (target.isReady) {
        // 已经加载过
        setTimeout(() => {
            callback();
        }, 0);
        return;
    }

    if (!target.depUrl) {
        // 没有外部依赖
        setTimeout(() => {
            callback();
        }, 0);
    } else {
        loadScript(target.depUrl, () => {
            target.isReady = true;
            callback();
        });
    }
};

对于想要异步加载的js文件,只需要类似xlsx.min.js配置:

const config = {
    'xlsx': {
        depUrl: '//js.guanmai.cn/build/libs/node_modules/xlsx/dist/xlsx.full.min.js',
        isReady: false    // 是否已经加载过,避免重复加载
    }
};

打开浏览器,再看一下网络面板。

打开主页,只请求了bundle.js,文件内容也是只包含了主页的代码。打开用到gm-xlsx库的页面,xlsx.min.js文件被加载进来了,并没有打包。并且再次应用gm-xlsx的功能时,xlsx.min.js并没有再次加载。

总结

xlsx的模块优化重点是:

其他

做这个需求因为涉及到多个库,踩了一些坑,总结一下:

Pines-Cheng commented 6 years ago

我们也碰到过这个问题,只不过我们的是 百度的富文本编辑器~

liyatang commented 6 years ago

@Pines-Cheng 你们怎么解决

Pines-Cheng commented 6 years ago

@liyatang 我们直接放在index.html加载,然后PC端要求没那么高,还没来得及进一步处理。

WaSheep commented 6 years ago

loadScript 的实现哪一个

HecateDK commented 6 years ago

@WaSheep loadScript这个function在gm-service/src/async_load_js 代码还有优化空间,不过大概思想就是那样了。

使用方式可以参考gm-service/src/require_module,里面的三个例子,我们都已应用到实际项目中了

WaSheep commented 6 years ago

@HecateDK js-xlsx 这样作为 chunk 还是太慢(大)了,题主有类似于 babel-lodash-plugin 的轮子么,来 demand loading 某些模块, 我正在尝试 treeShaking

HecateDK commented 6 years ago

@WaSheep 我们的做法和babel-plugin-lodash不太一样,我们只是把xlsx.min.js之类的比较大的文件,需要用到的时候,动态插入一个script标签去引入js。如果是第一次需要用到xlsx.min.js,确实是会比较慢的,因为要等浏览器下载资源,但这个等候时间,暂时来说我们是可以接受的。

这种情况下要如何去treeShaking,对此还没有比较好的思路。

liyatang commented 6 years ago

xlsx.min.js gzip 后才313kb,小事小事,对于现在的网络状况。 也许就一个图片的大小。 xlsx.min.js脱离后对webpack的减压,和网页速度优先响应等等才是关键指标。

alanhe421 commented 4 years ago

xlsx.min.js gzip 后才313kb,小事小事。

放在首屏加载这就不是小事了。