UNPKG

7.28 kBJavaScriptView Raw
1// @flow
2import type {
3 ResolvedParcelConfigFile,
4 FilePath,
5 Glob,
6 Transformer,
7 Resolver,
8 Bundler,
9 Namer,
10 Runtime,
11 EnvironmentContext,
12 PackageName,
13 Packager,
14 Optimizer,
15 Reporter,
16 Validator,
17} from '@parcel/types';
18import type {PackageManager} from '@parcel/package-manager';
19import {isMatch} from 'micromatch';
20import {basename} from 'path';
21import loadPlugin from './loadParcelPlugin';
22
23type Pipeline = Array<PackageName>;
24type GlobMap<T> = {[Glob]: T, ...};
25type SerializedParcelConfig = {|
26 $$raw: boolean,
27 config: ResolvedParcelConfigFile,
28 packageManager: PackageManager,
29|};
30
31export default class ParcelConfig {
32 packageManager: PackageManager;
33 filePath: FilePath;
34 resolvers: Pipeline;
35 transforms: GlobMap<Pipeline>;
36 bundler: PackageName;
37 namers: Pipeline;
38 runtimes: {[EnvironmentContext]: Pipeline, ...};
39 packagers: GlobMap<PackageName>;
40 validators: GlobMap<Pipeline>;
41 optimizers: GlobMap<Pipeline>;
42 reporters: Pipeline;
43 pluginCache: Map<PackageName, any>;
44
45 constructor(
46 config: ResolvedParcelConfigFile,
47 packageManager: PackageManager,
48 ) {
49 this.packageManager = packageManager;
50 this.filePath = config.filePath;
51 this.resolvers = config.resolvers || [];
52 this.transforms = config.transforms || {};
53 this.runtimes = config.runtimes || {};
54 this.bundler = config.bundler || '';
55 this.namers = config.namers || [];
56 this.packagers = config.packagers || {};
57 this.optimizers = config.optimizers || {};
58 this.reporters = config.reporters || [];
59 this.validators = config.validators || {};
60 this.pluginCache = new Map();
61 }
62
63 static deserialize(serialized: SerializedParcelConfig) {
64 return new ParcelConfig(serialized.config, serialized.packageManager);
65 }
66
67 getConfig() {
68 return {
69 filePath: this.filePath,
70 resolvers: this.resolvers,
71 transforms: this.transforms,
72 validators: this.validators,
73 runtimes: this.runtimes,
74 bundler: this.bundler,
75 namers: this.namers,
76 packagers: this.packagers,
77 optimizers: this.optimizers,
78 reporters: this.reporters,
79 };
80 }
81
82 serialize(): SerializedParcelConfig {
83 return {
84 $$raw: false,
85 packageManager: this.packageManager,
86 config: this.getConfig(),
87 };
88 }
89
90 loadPlugin(pluginName: PackageName) {
91 let plugin = this.pluginCache.get(pluginName);
92 if (plugin) {
93 return plugin;
94 }
95
96 plugin = loadPlugin(this.packageManager, pluginName, this.filePath);
97 this.pluginCache.set(pluginName, plugin);
98 return plugin;
99 }
100
101 loadPlugins<T>(
102 plugins: Pipeline,
103 ): Promise<
104 Array<{|
105 name: string,
106 plugin: T,
107 |}>,
108 > {
109 return Promise.all(
110 plugins.map(async pluginName => {
111 return {
112 name: pluginName,
113 plugin: await this.loadPlugin(pluginName),
114 };
115 }),
116 );
117 }
118
119 getResolverNames() {
120 if (this.resolvers.length === 0) {
121 throw new Error('No resolver plugins specified in .parcelrc config');
122 }
123
124 return this.resolvers;
125 }
126
127 getResolvers() {
128 return this.loadPlugins<Resolver>(this.getResolverNames());
129 }
130
131 getValidatorNames(filePath: FilePath): Array<string> {
132 let validators: Pipeline =
133 this.matchGlobMapPipelines(filePath, this.validators) || [];
134
135 return validators;
136 }
137
138 getTransformerNames(filePath: FilePath, pipeline?: ?string): Array<string> {
139 let transformers: Pipeline | null = this.matchGlobMapPipelines(
140 filePath,
141 this.transforms,
142 pipeline,
143 );
144 if (!transformers || transformers.length === 0) {
145 throw new Error(`No transformers found for "${filePath}".`);
146 }
147
148 return transformers;
149 }
150
151 getValidators(filePath: FilePath) {
152 let names = this.getValidatorNames(filePath);
153 return this.loadPlugins<Validator>(names);
154 }
155
156 getNamedPipelines(): $ReadOnlyArray<string> {
157 return Object.keys(this.transforms)
158 .filter(glob => glob.includes(':'))
159 .map(glob => glob.split(':')[0]);
160 }
161
162 getTransformers(filePath: FilePath, pipeline?: ?string) {
163 return this.loadPlugins<Transformer>(
164 this.getTransformerNames(filePath, pipeline),
165 );
166 }
167
168 getBundler(): Promise<Bundler> {
169 if (!this.bundler) {
170 throw new Error('No bundler specified in .parcelrc config');
171 }
172
173 return this.loadPlugin(this.bundler);
174 }
175
176 getNamers() {
177 if (this.namers.length === 0) {
178 throw new Error('No namer plugins specified in .parcelrc config');
179 }
180
181 return this.loadPlugins<Namer>(this.namers);
182 }
183
184 getRuntimes(
185 context: EnvironmentContext,
186 ): Promise<
187 Array<{|
188 name: string,
189 plugin: Runtime,
190 |}>,
191 > {
192 let runtimes = this.runtimes[context];
193 if (!runtimes) {
194 return Promise.resolve([]);
195 }
196
197 return this.loadPlugins<Runtime>(runtimes);
198 }
199
200 getPackagerName(filePath: FilePath): string {
201 let packagerName: ?PackageName = this.matchGlobMap(
202 filePath,
203 this.packagers,
204 );
205 if (!packagerName) {
206 throw new Error(`No packager found for "${filePath}".`);
207 }
208 return packagerName;
209 }
210
211 async getPackager(
212 filePath: FilePath,
213 ): Promise<{|
214 name: string,
215 plugin: Packager,
216 |}> {
217 let packagerName = this.getPackagerName(filePath);
218 return {
219 name: packagerName,
220 plugin: await this.loadPlugin(packagerName),
221 };
222 }
223
224 getOptimizerNames(filePath: FilePath, pipeline: ?string): Array<string> {
225 return (
226 this.matchGlobMapPipelines(filePath, this.optimizers, pipeline) ?? []
227 );
228 }
229
230 getOptimizers(
231 filePath: FilePath,
232 pipeline: ?string,
233 ): Promise<
234 Array<{|
235 name: string,
236 plugin: Optimizer,
237 |}>,
238 > {
239 let optimizers = this.getOptimizerNames(filePath, pipeline);
240 if (optimizers.length === 0) {
241 return Promise.resolve([]);
242 }
243
244 return this.loadPlugins<Optimizer>(optimizers);
245 }
246
247 getReporters() {
248 return this.loadPlugins<Reporter>(this.reporters);
249 }
250
251 isGlobMatch(filePath: FilePath, pattern: Glob, pipeline?: ?string) {
252 let prefix = pipeline ? `${pipeline}:` : '';
253 return (
254 isMatch(prefix + filePath, pattern) ||
255 isMatch(prefix + basename(filePath), pattern)
256 );
257 }
258
259 matchGlobMap(filePath: FilePath, globMap: {[Glob]: any, ...}) {
260 for (let pattern in globMap) {
261 if (this.isGlobMatch(filePath, pattern)) {
262 return globMap[pattern];
263 }
264 }
265
266 return null;
267 }
268
269 matchGlobMapPipelines(
270 filePath: FilePath,
271 globMap: {[Glob]: Pipeline, ...},
272 pipeline?: ?string,
273 ) {
274 let matches = [];
275 for (let pattern in globMap) {
276 if (this.isGlobMatch(filePath, pattern, pipeline)) {
277 matches.push(globMap[pattern]);
278 }
279 }
280
281 let flatten = () => {
282 let pipeline = matches.shift() || [];
283 let spreadIndex = pipeline.indexOf('...');
284 if (spreadIndex >= 0) {
285 pipeline = [
286 ...pipeline.slice(0, spreadIndex),
287 ...flatten(),
288 ...pipeline.slice(spreadIndex + 1),
289 ];
290 }
291
292 if (pipeline.includes('...')) {
293 throw new Error(
294 'Only one spread parameter can be included in a config pipeline',
295 );
296 }
297
298 return pipeline;
299 };
300
301 let res = flatten();
302 return res;
303 }
304}