ArthurYung / my-voice

分享&吐槽
2 stars 0 forks source link

@2020-2-8[记一次执着的require] #11

Open ArthurYung opened 4 years ago

ArthurYung commented 4 years ago

写在前面

在项目中用到了highlight.js对代码块进行高亮,打包一看发现它竟然有1M+的大小,优化之。 28B3D08F-A30C-4C8F-85CF-2E5C9B623544

优化分析。

首先我们看下highlight.js依赖的package.json

  "repository": {
    "type": "git",
    "url": "git://github.com/highlightjs/highlight.js.git"
  },
  "main": "./lib/index.js",
  "scripts": {
    "mocha": "mocha",
    "test": "mocha --globals document test",
    "test-browser": "mocha --globals document test/browser"
  },

顺藤摸瓜,我们再康康mian入口定义的./lib/index.js

var hljs = require('./highlight');

hljs.registerLanguage('1c', require('./languages/1c'));
hljs.registerLanguage('abnf', require('./languages/abnf'));
hljs.registerLanguage('accesslog', require('./languages/accesslog'));
hljs.registerLanguage('actionscript', require('./languages/actionscript'));
hljs.registerLanguage('ada', require('./languages/ada'));
hljs.registerLanguage('angelscript', require('./languages/angelscript'));
hljs.registerLanguage('apache', require('./languages/apache'));
hljs.registerLanguage('applescript', require('./languages/applescript'));
... 100+

`module.exports = hljs;`

入口文件中将所有的语言解析方法都引入了所以才这么大,我们只需要选择性引入就能达到优化体积的效果。

第一次require

经过分析,我们只需要手动引入hljs的主函数文件,然后使用registerLanguageAPI添加相应的语种就行。 因为highlightAuto方法需要指定languages数组,所以这次我先定义语种名称的数组,然后循环引入,方便又简单,让我们康康效果。

import * as hljs from 'highlight.js/lib/highlight';

const languages = [
  'apache', 'bash', 'cs', 'cpp', 'css', 'coffeescript', 'diff', 'xml', 'http', 'ini',
  'json', 'java', 'javascript', 'makefile', 'markdown', 'nginx', 'objectivec', 'ruby', 
  'perl', 'php', 'python', 'sql', 'handlebars',
];

languages.forEach(name => {
    hljs.registerLanguage(name, require('highlight.js/lib/languages/' + name));
})

28B3D08F-A30C-4C8F-85CF-2E5C9B623544 额。。似乎并没有效果。。。

第二次require

为什么会这样,我大致了解了一下require函数的执行机制 1、先从缓存中读取,如果没有则继续往下

2、判断需要模块路径是否以/结尾,如果不是,则要判断

    a. 检查是否是一个文件,如果是,则转换为真实路径

    b. 否则如果是一个目录,则调用tryPackage方法读取该目录下的package.json文件,把里面的main属性设置为filename

    c. 如果没有读到路径上的文件,则通过tryExtensions尝试在该路径后依次加上.js,.json和.node后缀,判断是否存在,若存在则返回加上后缀后的路径

3、如果依然不存在,则同样调用tryPackage方法读取该目录下的package.json文件,把里面的main属性设置为filename

4、如果依然不存在,则尝试在该路径后依次加上index.js,index.json和index.node,判断是否存在,若存在则返回拼接后的路径。

5、若解析成功,则把解析得到的文件名cache起来,下次require就不用再次解析了,否则若解析失败,则返回false

在webpack打包的过程中是不会执行代码里的上下文的,所以require匹配不到文件的真实路径从而引入了入口文件。 我改为手动引入真实路径,来验证一下。

hljs.registerLanguage('apache', require('highlight.js/lib/languages/apache'));
hljs.registerLanguage('bash', require('highlight.js/lib/languages/bash'));
hljs.registerLanguage('cs', require('highlight.js/lib/languages/cs'));
hljs.registerLanguage('cpp', require('highlight.js/lib/languages/cpp'));
...

D88AE749-5CCE-4117-8F0D-6E6C4CF6D3DF 成功了,代码也很直观好懂。但是我不甘心,不甘心多写这么多代码,于是乎我又开始了。

第三次require

怎么办到选择性的批量引入文件呢?require.context可以做到,通过设置第三个参数的正则筛选出我想要的资源。

require.context(''highlight.js/lib/languages', false, /(cs|apache|bash|cpp|js|......|python)\.js$/)

这样引入是成功了,但是正则里这么一坨要怎么维护? OK,既然编译过程中识别不了上下文中的变量,那我就设置一个编译过程中存在的变量。 首先在配置文件中加入一个languaes属性用来后期维护。

{
    ...
    languaes: 'cs|cpp|css|coffeescript|diff|xml|http|ini|nginx|objectivec|ruby|perl|php|handlebars'
}

然后我们把这个属性通过webpack.DefinePlugin插件暴露到编译生命周期。

const { languages } = require('./config');

{
 plugins: [
 new webpack.DefinePlugin({
        LANGUAGES: JSON.stringify(languages.split('|')),
        LANG_REQUIRE_REG: '/(' + languages + ')\.js/'
    })
 ]
}

hljs.js

import * as hljs from 'highlight.js/lib/highlight';

const context = require.context('highlight.js/lib/languages', false, LANG_REQUIRE_REG)

context.keys().forEach(key => {
    hljs.registerLanguage(key.match(/([^\/]+)?\.js$/)[1], context(key))
})

hljs.configure({
  languages: LANGUAGES,
});

参考文章

深入理解node中require的原理及执行过程