UNPKG

4.12 kBPlain TextView Raw
1import Plugin, { Tree } from 'broccoli-plugin';
2import makeDebug from 'debug';
3import WebpackBundler from './webpack';
4import Splitter, { BundleDependencies } from './splitter';
5import Package, { reloadDevPackages } from './package';
6import { merge } from 'lodash';
7import { join } from 'path';
8import {
9 readFileSync,
10 writeFileSync,
11 emptyDirSync,
12 copySync,
13} from 'fs-extra';
14import BundleConfig from './bundle-config';
15
16const debug = makeDebug('ember-auto-import:bundler');
17
18export interface BundlerPluginOptions {
19 consoleWrite: (msg: string) => void;
20 environment: "development" | "test" | "production";
21 splitter: Splitter;
22 packages: Set<Package>;
23 bundles: BundleConfig;
24}
25
26export interface BuildResult {
27 entrypoints: Map<string, string[]>;
28 lazyAssets: string[];
29 dir: string;
30}
31
32export interface BundlerHook {
33 build(modules: Map<string, BundleDependencies>): Promise<BuildResult>;
34}
35
36export default class Bundler extends Plugin {
37 private lastDeps: Map<string, BundleDependencies> | undefined;
38 private cachedBundlerHook: BundlerHook | undefined;
39 private didEnsureDirs = false;
40
41 constructor(allAppTree: Tree, private options: BundlerPluginOptions) {
42 super([allAppTree], {
43 persistentOutput: true,
44 needsCache: true
45 });
46 }
47
48 private get publicAssetURL(): string | undefined {
49 // Only the app (not an addon) can customize the public asset URL, because
50 // it's an app concern.
51 let rootPackage = [...this.options.packages.values()].find(
52 pkg => !pkg.isAddon
53 );
54 if (rootPackage) {
55 let url = rootPackage.publicAssetURL;
56 if (url) {
57 if (url[url.length - 1] !== '/') {
58 url = url + '/';
59 }
60 return url;
61 }
62 }
63 }
64
65 get bundlerHook(): BundlerHook {
66 if (!this.cachedBundlerHook) {
67 let extraWebpackConfig = merge(
68 {},
69 ...[...this.options.packages.values()].map(pkg => pkg.webpackConfig)
70 );
71 if ([...this.options.packages.values()].find(pkg => pkg.forbidsEval)) {
72 extraWebpackConfig.devtool = 'source-map';
73 }
74 debug('extraWebpackConfig %j', extraWebpackConfig);
75 this.cachedBundlerHook = new WebpackBundler(
76 this.options.bundles,
77 this.options.environment,
78 extraWebpackConfig,
79 this.options.consoleWrite,
80 this.publicAssetURL,
81 this.cachePath
82 );
83 }
84 return this.cachedBundlerHook;
85 }
86
87 async build() {
88 this.ensureDirs();
89 reloadDevPackages();
90 let { splitter } = this.options;
91 let bundleDeps = await splitter.deps();
92 if (bundleDeps !== this.lastDeps) {
93 let buildResult = await this.bundlerHook.build(bundleDeps);
94 this.addEntrypoints(buildResult);
95 this.addLazyAssets(buildResult);
96 this.lastDeps = bundleDeps;
97 }
98 }
99
100 private ensureDirs() {
101 if (this.didEnsureDirs) {
102 return;
103 }
104 emptyDirSync(join(this.outputPath, 'lazy'));
105 for (let bundle of this.options.bundles.names) {
106 emptyDirSync(join(this.outputPath, 'entrypoints', bundle));
107 }
108 this.didEnsureDirs = true;
109 }
110
111 private addEntrypoints({ entrypoints, dir }: BuildResult) {
112 for (let bundle of this.options.bundles.names) {
113 if (entrypoints.has(bundle)) {
114 entrypoints
115 .get(bundle)!
116 .forEach(asset => {
117 copySync(join(dir, asset), join(this.outputPath, 'entrypoints', bundle, asset));
118 });
119 }
120 }
121 }
122
123 private addLazyAssets({ lazyAssets, dir }: BuildResult) {
124 let contents = lazyAssets.map(asset => {
125 // we copy every lazy asset into place here
126 let content = readFileSync(join(dir, asset));
127 writeFileSync(join(this.outputPath, 'lazy', asset), content);
128
129 // and then for JS assets, we also save a copy to put into the fastboot
130 // combined bundle. We don't want to include other things like WASM here
131 // that can't be concatenated.
132 if (/\.js$/i.test(asset)) {
133 return content;
134 }
135
136 }).filter(Boolean);
137 writeFileSync(
138 join(this.outputPath, 'lazy', 'auto-import-fastboot.js'),
139 contents.join('\n')
140 );
141 }
142}