UNPKG

6.05 kBJavaScriptView Raw
1// @flow
2import type {
3 FilePath,
4 ParcelConfigFile,
5 ResolvedParcelConfigFile,
6 PackageName,
7} from '@parcel/types';
8import type {ParcelOptions} from './types';
9import {resolveConfig, resolve, validateSchema} from '@parcel/utils';
10import {parse} from 'json5';
11import path from 'path';
12import assert from 'assert';
13
14import ParcelConfig from './ParcelConfig';
15import ParcelConfigSchema from './ParcelConfig.schema';
16
17type Pipeline = Array<PackageName>;
18type ConfigMap<K, V> = {[K]: V, ...};
19
20export default async function loadParcelConfig(
21 filePath: FilePath,
22 options: ParcelOptions,
23) {
24 // Resolve plugins from cwd when a config is passed programmatically
25 let parcelConfig = options.config
26 ? await create(
27 {
28 ...options.config,
29 resolveFrom: options.inputFS.cwd(),
30 },
31 options,
32 )
33 : await resolveParcelConfig(filePath, options);
34 if (!parcelConfig && options.defaultConfig) {
35 parcelConfig = await create(
36 {
37 ...options.defaultConfig,
38 resolveFrom: options.inputFS.cwd(),
39 },
40 options,
41 );
42 }
43
44 if (!parcelConfig) {
45 throw new Error('Could not find a .parcelrc');
46 }
47
48 return parcelConfig;
49}
50
51export async function resolveParcelConfig(
52 filePath: FilePath,
53 options: ParcelOptions,
54) {
55 let configPath = await resolveConfig(options.inputFS, filePath, [
56 '.parcelrc',
57 ]);
58 if (!configPath) {
59 return null;
60 }
61
62 return readAndProcess(configPath, options);
63}
64
65export function create(
66 config: ResolvedParcelConfigFile,
67 options: ParcelOptions,
68) {
69 return processConfig(config, config.filePath, options);
70}
71
72export async function readAndProcess(
73 configPath: FilePath,
74 options: ParcelOptions,
75) {
76 let config: ParcelConfigFile = parse(
77 await options.inputFS.readFile(configPath),
78 );
79 return processConfig(config, configPath, options);
80}
81
82export async function processConfig(
83 configFile: ParcelConfigFile | ResolvedParcelConfigFile,
84 filePath: FilePath,
85 options: ParcelOptions,
86) {
87 let resolvedFile: ResolvedParcelConfigFile = {filePath, ...configFile};
88 let config = new ParcelConfig(resolvedFile, options.packageManager);
89 let relativePath = path.relative(options.inputFS.cwd(), filePath);
90 validateConfigFile(configFile, relativePath);
91
92 let extendedFiles: Array<FilePath> = [];
93
94 if (configFile.extends) {
95 let exts = Array.isArray(configFile.extends)
96 ? configFile.extends
97 : [configFile.extends];
98 for (let ext of exts) {
99 let resolved = await resolveExtends(ext, filePath, options);
100 extendedFiles.push(resolved);
101 let {
102 extendedFiles: moreExtendedFiles,
103 config: baseConfig,
104 } = await readAndProcess(resolved, options);
105 extendedFiles = extendedFiles.concat(moreExtendedFiles);
106 config = mergeConfigs(baseConfig, resolvedFile);
107 }
108 }
109
110 return {config, extendedFiles};
111}
112
113export async function resolveExtends(
114 ext: string,
115 configPath: FilePath,
116 options: ParcelOptions,
117) {
118 if (ext.startsWith('.')) {
119 return path.resolve(path.dirname(configPath), ext);
120 } else {
121 let {resolved} = await resolve(options.inputFS, ext, {
122 basedir: path.dirname(configPath),
123 extensions: ['.json'],
124 });
125 return options.inputFS.realpath(resolved);
126 }
127}
128
129export function validateConfigFile(
130 config: ParcelConfigFile | ResolvedParcelConfigFile,
131 relativePath: FilePath,
132) {
133 validateNotEmpty(config, relativePath);
134
135 validateSchema.diagnostic(
136 ParcelConfigSchema,
137 config,
138 relativePath,
139 JSON.stringify(config, null, '\t'),
140 '@parcel/core',
141 '',
142 'Invalid Parcel Config',
143 );
144}
145
146export function validateNotEmpty(
147 config: ParcelConfigFile | ResolvedParcelConfigFile,
148 relativePath: FilePath,
149) {
150 assert.notDeepStrictEqual(config, {}, `${relativePath} can't be empty`);
151}
152
153export function mergeConfigs(
154 base: ParcelConfig,
155 ext: ResolvedParcelConfigFile,
156): ParcelConfig {
157 return new ParcelConfig(
158 {
159 filePath: ext.filePath, // TODO: revisit this - it should resolve plugins based on the actual config they are defined in
160 resolvers: mergePipelines(base.resolvers, ext.resolvers),
161 transforms: mergeMaps(base.transforms, ext.transforms, mergePipelines),
162 validators: mergeMaps(base.validators, ext.validators, mergePipelines),
163 bundler: ext.bundler || base.bundler,
164 namers: mergePipelines(base.namers, ext.namers),
165 runtimes: mergeMaps(base.runtimes, ext.runtimes),
166 packagers: mergeMaps(base.packagers, ext.packagers),
167 optimizers: mergeMaps(base.optimizers, ext.optimizers, mergePipelines),
168 reporters: mergePipelines(base.reporters, ext.reporters),
169 },
170 base.packageManager,
171 );
172}
173
174export function mergePipelines(base: ?Pipeline, ext: ?Pipeline): Pipeline {
175 if (!ext) {
176 return base || [];
177 }
178
179 if (base) {
180 // Merge the base pipeline if a rest element is defined
181 let spreadIndex = ext.indexOf('...');
182 if (spreadIndex >= 0) {
183 if (ext.filter(v => v === '...').length > 1) {
184 throw new Error(
185 'Only one spread element can be included in a config pipeline',
186 );
187 }
188
189 ext = [
190 ...ext.slice(0, spreadIndex),
191 ...(base || []),
192 ...ext.slice(spreadIndex + 1),
193 ];
194 }
195 }
196
197 return ext;
198}
199
200export function mergeMaps<K, V>(
201 base: ?ConfigMap<K, V>,
202 ext: ?ConfigMap<K, V>,
203 merger?: (a: V, b: V) => V,
204): ConfigMap<K, V> {
205 if (!ext) {
206 return base || {};
207 }
208
209 if (!base) {
210 return ext;
211 }
212
213 // Add the extension options first so they have higher precedence in the output glob map
214 let res: ConfigMap<K, V> = {};
215 for (let k in ext) {
216 // Flow doesn't correctly infer the type. See https://github.com/facebook/flow/issues/1736.
217 let key: K = (k: any);
218 res[key] = merger && base[key] ? merger(base[key], ext[key]) : ext[key];
219 }
220
221 // Add base options that aren't defined in the extension
222 for (let k in base) {
223 let key: K = (k: any);
224 if (!res[key]) {
225 res[key] = base[key];
226 }
227 }
228
229 return res;
230}