Каноническим сообщением для этого ответа является Gist.
TL; DR
Создать mylib/index_browser.js
window.mylib = {
inc: require('./inc'),
utils: {
is_number: require('./is_number')
}
};
Создать mylib/externs.js
/** @externs */
var mylib;
var inc;
var utils;
var is_number;
Затем:
$ cc --compilation_level ADVANCED \
--language_out ES5 \
--process_common_js_modules \
--module_resolution NODE \
--externs mylib/externs.js \
--isolation_mode IIFE \
--js mylib/index_browser.js mylib/inc.js mylib/is_number.js \
--js_output_file mylib/browser.min.js
Где cc
— это псевдоним вашего экземпляра Google Closure Compiler; см. пример ниже
Прежде чем мы начнем:
Я написал этот псевдоним, чтобы упростить вызов Google Closure Compiler (CC).
$ alias cc="java -jar /devtools/closure-compiler/compiler.jar"
$ cc --version
Closure Compiler (http://github.com/google/closure-compiler)
Version: v20210106
Браузерная версия библиотеки будет скомпилирована до ES5.
Пошаговые инструкции
Ваша первая попытка может выглядеть так: просто скомпилируйте файл экспорта mylib/index.js
$ cc --compilation_level ADVANCED \
--language_out ES5 \
--js mylib/index.js
mylib/index.js:1:0: ERROR - [JSC_UNDEFINED_VARIABLE] variable module is undeclared
1| module.exports = {
^^^^^^
mylib/index.js:2:7: ERROR - [JSC_UNDEFINED_VARIABLE] variable require is undeclared
2| inc: require('./inc'),
^^^^^^^
2 error(s), 0 warning(s)
Если КК не знает о module
и require
, это не самое лучшее начало.
К счастью, нам не хватает только флага --process_common_js_modules
:
$ cc --compilation_level ADVANCED \
--language_out ES5 \
--process_common_js_modules \
--js mylib/index.js
mylib/index.js:2:7: ERROR - [JSC_JS_MODULE_LOAD_WARNING] Failed to load module "./inc"
2| inc: require('./inc'),
^
mylib/index.js:4:15: ERROR - [JSC_JS_MODULE_LOAD_WARNING] Failed to load module "./is_number"
4| is_number: require('./is_number')
^
2 error(s), 0 warning(s)
Все еще не очень хорошо, но на этот раз ошибки другие:
- Кики не знает, о каком
require
ты говоришь
- CC не знает, где находятся эти два других модуля.
Нам нужен флаг --module_resolution
и указать CC, где находятся остальные модули:
$ cc --compilation_level ADVANCED \
--language_out ES5 \
--process_common_js_modules \
--module_resolution NODE \
--js mylib/index.js mylib/inc.js mylib/is_number.js
However the output is empty...
Why? In ADVANCED
compilation mode CC removes any code that is not used. Which is the case actually: so far all this stuff isn't used at all!
Давайте проверим с менее агрессивным режимом компиляции:
$ cc --compilation_level WHITESPACE_ONLY --formatting PRETTY_PRINT \
--language_out ES5 \
--process_common_js_modules \
--module_resolution NODE \
--js mylib/index.js mylib/inc.js mylib/is_number.js
var module$mylib$index = {default:{}};
module$mylib$index.default.inc = module$mylib$inc.default;
module$mylib$index.default.utils = {is_number:module$mylib$is_number.default};
var module$mylib$inc = {};
var is_number$$module$mylib$inc = module$mylib$is_number.default;
module$mylib$inc.default = function(x) {
return (0,module$mylib$is_number.default)(x) ? x + 1 : x;
};
var module$mylib$is_number = {};
module$mylib$is_number.default = function(x) {
return typeof x === "number";
};
Мы видим, что даже если бы режим компиляции ADVANCED
не удалял все, это все равно не было бы очень полезным. Где window.mylib
например?
Единственный способ, которым мне удалось получить доступ к моей библиотеке по адресу window.mylib
и скомпилировать с самым агрессивным режимом компиляции, — это иметь отдельный файл экспорта для браузера.
Из этого mylib/index.js
module.exports = {
inc: require('./inc'),
utils: {
is_number: require('./is_number')
}
};
К этому mylib/index_browser.js
window.mylib = {
inc: require('./inc'),
utils: {
is_number: require('./is_number')
}
};
Когда вы добавляете к объекту window
, CC знает, что этот код может быть достигнут, поэтому он больше не может безопасно удалить его.
Попробуем еще раз с этим файлом:
$ cc --compilation_level ADVANCED --formatting PRETTY_PRINT \
--language_out ES5 \
--process_common_js_modules \
--module_resolution NODE \
--js mylib/index_browser.js mylib/inc.js mylib/is_number.js
function b(a) {
return "number" === typeof a;
}
;window.g = {h:function(a) {
return b(a) ? a + 1 : a;
}, j:{i:b}};
Выглядит лучше, но есть большая проблема: CC исказил все имена!
Не волнуйся! Нам нужно только сказать, какие имена CC следует оставить в покое. Это цель файла externs.
mylib/externs.js
/** @externs */
var foo;
var inc;
var utils;
var is_number;
Нам нужен еще один флаг: --externs
$ cc --compilation_level ADVANCED --formatting PRETTY_PRINT \
--language_out ES5 \
--process_common_js_modules \
--module_resolution NODE \
--externs mylib/externs.js \
--js mylib/index_browser.js mylib/inc.js mylib/is_number.js
function b(a) {
return "number" === typeof a;
}
;window.mylib = {inc:function(a) {
return b(a) ? a + 1 : a;
}, utils:{is_number:b}};
Попасть туда...
Одно из очевидных улучшений заключается в том, чтобы обернуть все это в IIFE, чтобы избежать чрезмерного загрязнения глобальной области видимости.
Нам нужен флаг --isolation_mode
:
$ cc --compilation_level ADVANCED --formatting PRETTY_PRINT \
--language_out ES5 \
--process_common_js_modules \
--module_resolution NODE \
--externs mylib/externs.js \
--isolation_mode IIFE \
--js mylib/index_browser.js mylib/inc.js mylib/is_number.js
(function(){function b(a) {
return "number" === typeof a;
}
;window.mylib = {inc:function(a) {
return b(a) ? a + 1 : a;
}, utils:{is_number:b}};
}).call(this);
Фантастика!
Все, что осталось сделать, это сохранить это в файл и удалить форматирование, чтобы сэкономить несколько лишних байтов:
$ cc --compilation_level ADVANCED \
--language_out ES5 \
--process_common_js_modules \
--module_resolution NODE \
--externs mylib/externs.js \
--isolation_mode IIFE \
--js mylib/index_browser.js mylib/inc.js mylib/is_number.js \
--js_output_file mylib/browser.min.js
mylib/browser.min.js
(function(){function b(a){return"number"===typeof a};window.mylib={inc:function(a){return b(a)?a+1:a},utils:{is_number:b}};}).call(this);
person
customcommander
schedule
02.02.2021