1 | import Plugin, { Tree } from 'broccoli-plugin';
|
2 | import makeDebug from 'debug';
|
3 | import WebpackBundler from './webpack';
|
4 | import Splitter, { BundleDependencies } from './splitter';
|
5 | import Package, { reloadDevPackages } from './package';
|
6 | import { merge } from 'lodash';
|
7 | import { join } from 'path';
|
8 | import {
|
9 | readFileSync,
|
10 | writeFileSync,
|
11 | emptyDirSync,
|
12 | copySync,
|
13 | } from 'fs-extra';
|
14 | import BundleConfig from './bundle-config';
|
15 |
|
16 | const debug = makeDebug('ember-auto-import:bundler');
|
17 |
|
18 | export interface BundlerPluginOptions {
|
19 | consoleWrite: (msg: string) => void;
|
20 | environment: "development" | "test" | "production";
|
21 | splitter: Splitter;
|
22 | packages: Set<Package>;
|
23 | bundles: BundleConfig;
|
24 | }
|
25 |
|
26 | export interface BuildResult {
|
27 | entrypoints: Map<string, string[]>;
|
28 | lazyAssets: string[];
|
29 | dir: string;
|
30 | }
|
31 |
|
32 | export interface BundlerHook {
|
33 | build(modules: Map<string, BundleDependencies>): Promise<BuildResult>;
|
34 | }
|
35 |
|
36 | export 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 |
|
50 |
|
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 |
|
126 | let content = readFileSync(join(dir, asset));
|
127 | writeFileSync(join(this.outputPath, 'lazy', asset), content);
|
128 |
|
129 |
|
130 |
|
131 |
|
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 | }
|