Open ArthurYung opened 4 years ago
在项目中用到了highlight.js对代码块进行高亮,打包一看发现它竟然有1M+的大小,优化之。
highlight.js
首先我们看下highlight.js依赖的package.json
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
./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;`
入口文件中将所有的语言解析方法都引入了所以才这么大,我们只需要选择性引入就能达到优化体积的效果。
经过分析,我们只需要手动引入hljs的主函数文件,然后使用registerLanguageAPI添加相应的语种就行。 因为highlightAuto方法需要指定languages数组,所以这次我先定义语种名称的数组,然后循环引入,方便又简单,让我们康康效果。
hljs
registerLanguage
highlightAuto
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)); })
额。。似乎并没有效果。。。
为什么会这样,我大致了解了一下require函数的执行机制 1、先从缓存中读取,如果没有则继续往下
require
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')); ...
成功了,代码也很直观好懂。但是我不甘心,不甘心多写这么多代码,于是乎我又开始了。
怎么办到选择性的批量引入文件呢?require.context可以做到,通过设置第三个参数的正则筛选出我想要的资源。
require.context(''highlight.js/lib/languages', false, /(cs|apache|bash|cpp|js|......|python)\.js$/)
这样引入是成功了,但是正则里这么一坨要怎么维护? OK,既然编译过程中识别不了上下文中的变量,那我就设置一个编译过程中存在的变量。 首先在配置文件中加入一个languaes属性用来后期维护。
languaes
{ ... languaes: 'cs|cpp|css|coffeescript|diff|xml|http|ini|nginx|objectivec|ruby|perl|php|handlebars' }
然后我们把这个属性通过webpack.DefinePlugin插件暴露到编译生命周期。
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的原理及执行过程
写在前面
在项目中用到了
highlight.js
对代码块进行高亮,打包一看发现它竟然有1M+的大小,优化之。优化分析。
首先我们看下
highlight.js
依赖的package.json
顺藤摸瓜,我们再康康mian入口定义的
./lib/index.js
入口文件中将所有的语言解析方法都引入了所以才这么大,我们只需要选择性引入就能达到优化体积的效果。
第一次require
经过分析,我们只需要手动引入
hljs
的主函数文件,然后使用registerLanguage
API添加相应的语种就行。 因为highlightAuto
方法需要指定languages数组,所以这次我先定义语种名称的数组,然后循环引入,方便又简单,让我们康康效果。额。。似乎并没有效果。。。
第二次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匹配不到文件的真实路径从而引入了入口文件。 我改为手动引入真实路径,来验证一下。
成功了,代码也很直观好懂。但是我不甘心,不甘心多写这么多代码,于是乎我又开始了。
第三次require
怎么办到选择性的批量引入文件呢?require.context可以做到,通过设置第三个参数的正则筛选出我想要的资源。
这样引入是成功了,但是正则里这么一坨要怎么维护? OK,既然编译过程中识别不了上下文中的变量,那我就设置一个编译过程中存在的变量。 首先在配置文件中加入一个
languaes
属性用来后期维护。然后我们把这个属性通过
webpack.DefinePlugin
插件暴露到编译生命周期。hljs.js
参考文章
深入理解node中require的原理及执行过程