-
Notifications
You must be signed in to change notification settings - Fork 6
/
bundler.js
170 lines (145 loc) · 4.68 KB
/
bundler.js
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"use strict";
let { generateError } = require("./util");
let rollup = require("rollup");
let commonjs = require("rollup-plugin-commonjs");
let nodeResolve = require("rollup-plugin-node-resolve");
let fs = require("fs");
const DEFAULTS = {
format: "iife"
};
let BUNDLES = {}; // configuration and state by entry point
// TODO:
// * minification support
// * `aliases` (Rollup: `paths`?)
// * source maps?
// * minification light: only stripping comments
module.exports = (callback, { compact }, ...bundles) => {
bundles.forEach(config => {
// initialize configuration/state cache
let { entryPoint } = config;
config = Object.assign({ compact }, DEFAULTS, config);
BUNDLES[entryPoint] = {
rollup: generateConfig(config),
bundle: null, // Rollup cache
files: [fs.realpathSync(entryPoint)]
};
generateBundle(entryPoint, callback);
});
return rebundler(callback);
};
function rebundler(callback) {
return filepath => {
Object.keys(BUNDLES).forEach(entryPoint => {
let cache = BUNDLES[entryPoint];
if(cache.bundle === null) { // initial compilation still in progress
return;
}
if(cache.files.includes(filepath)) {
generateBundle(entryPoint, callback);
}
});
};
}
function generateBundle(entryPoint, callback) {
let cache = BUNDLES[entryPoint];
let { readConfig, writeConfig } = cache.rollup;
let options = Object.assign({}, readConfig, {
entry: entryPoint,
cache: cache.bundle
});
return rollup.rollup(options).
then(bundle => {
cache.files = bundle.modules.reduce(collectModulePaths, []);
cache.bundle = bundle;
return bundle.generate(writeConfig).code;
}).
catch(err => {
// also report error from within bundle, to avoid it being overlooked
let code = generateError(err);
return { code, error: true };
}).
then(code => void callback(entryPoint, code));
}
// generates Rollup configuration
// * `extensions` is a list of additional file extensions for loading modules
// (e.g. `[".jsx"]`)
// * `externals` determines which modules to exclude from the bundle
// (e.g. `{ jquery: "jQuery" }` - the key represents the respective module
// name, the value refers to the corresponding global variable)
// * `format` determines the bundle format (defaults to IIFE); cf.
// https://github.com/rollup/rollup/wiki/JavaScript-API#format
// * `compact`, if truthy, compresses the bundle's code while retaining the
// source code's original structure
// * `moduleName` determines the global variable to hold the entry point's
// exports (if any)
// * `transpiler.features` determines the language features to be supported
// within source code (e.g. `["es2015", "jsx"]`)
// * `transpiler.jsx` are JSX-specific options (e.g. `{ pragma: "createElement" }`)
// * `transpiler.exclude` is a list of modules for which to skip transpilation
// (e.g. `["jquery"]`, perhaps due to an already optimized ES5 distribution)
function generateConfig({ extensions, externals, // eslint-disable-next-line indent
format, compact, moduleName, transpiler }) {
let resolve = { jsnext: true };
if(extensions) {
resolve.extensions = [".js"].concat(extensions);
}
if(transpiler) {
let babel = require("rollup-plugin-babel");
let settings = generateTranspilerConfig(transpiler);
transpiler = babel(settings);
}
let plugins = (transpiler ? [transpiler] : []).concat([
nodeResolve(resolve),
commonjs({ include: "node_modules/**" })
]);
if(compact) {
let cleanup = require("rollup-plugin-cleanup");
plugins = plugins.concat(cleanup());
}
let cfg = { format, plugins };
if(moduleName) {
cfg.moduleName = moduleName;
}
if(externals) { // excluded from bundle
cfg.external = Object.keys(externals);
cfg.globals = externals;
}
// distinguish between (roughly) read and write settings
let read = ["external", "paths", "plugins"];
return Object.keys(cfg).reduce((memo, key) => {
let type = read.includes(key) ? "readConfig" : "writeConfig";
memo[type][key] = cfg[key];
return memo;
}, {
readConfig: {},
writeConfig: {
// XXX: temporary shim; cf. http://2ality.com/2016/09/global.html
intro: "var global = window;"
}
});
}
function generateTranspilerConfig({ features, jsx = {}, exclude }) {
let settings = exclude ? { exclude } : {};
if(features.includes("es2015")) {
settings.presets = ["es2015-rollup"];
}
if(features.includes("jsx")) {
settings.plugins = ["transform-react-jsx", jsx];
}
return settings;
}
// adapted from Rollup
function collectModulePaths(memo, module) {
let filepath = module.id;
// skip plugin helper modules
if(/\0/.test(filepath)) {
return memo;
}
// resolve symlinks to avoid duplicate watchers
try {
filepath = fs.realpathSync(filepath);
} catch(err) {
return memo;
}
return memo.concat(filepath);
}