1 |
|
2 | import type {
|
3 | FilePath,
|
4 | ParcelConfigFile,
|
5 | ResolvedParcelConfigFile,
|
6 | PackageName,
|
7 | } from '@parcel/types';
|
8 | import type {ParcelOptions} from './types';
|
9 | import {resolveConfig, resolve, validateSchema} from '@parcel/utils';
|
10 | import {parse} from 'json5';
|
11 | import path from 'path';
|
12 | import assert from 'assert';
|
13 |
|
14 | import ParcelConfig from './ParcelConfig';
|
15 | import ParcelConfigSchema from './ParcelConfig.schema';
|
16 |
|
17 | type Pipeline = Array<PackageName>;
|
18 | type ConfigMap<K, V> = {[K]: V, ...};
|
19 |
|
20 | export default async function loadParcelConfig(
|
21 | filePath: FilePath,
|
22 | options: ParcelOptions,
|
23 | ) {
|
24 |
|
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 |
|
51 | export 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 |
|
65 | export function create(
|
66 | config: ResolvedParcelConfigFile,
|
67 | options: ParcelOptions,
|
68 | ) {
|
69 | return processConfig(config, config.filePath, options);
|
70 | }
|
71 |
|
72 | export 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 |
|
82 | export 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 |
|
113 | export 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 |
|
129 | export 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 |
|
146 | export function validateNotEmpty(
|
147 | config: ParcelConfigFile | ResolvedParcelConfigFile,
|
148 | relativePath: FilePath,
|
149 | ) {
|
150 | assert.notDeepStrictEqual(config, {}, `${relativePath} can't be empty`);
|
151 | }
|
152 |
|
153 | export function mergeConfigs(
|
154 | base: ParcelConfig,
|
155 | ext: ResolvedParcelConfigFile,
|
156 | ): ParcelConfig {
|
157 | return new ParcelConfig(
|
158 | {
|
159 | filePath: ext.filePath,
|
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 |
|
174 | export function mergePipelines(base: ?Pipeline, ext: ?Pipeline): Pipeline {
|
175 | if (!ext) {
|
176 | return base || [];
|
177 | }
|
178 |
|
179 | if (base) {
|
180 |
|
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 |
|
200 | export 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 |
|
214 | let res: ConfigMap<K, V> = {};
|
215 | for (let k in ext) {
|
216 |
|
217 | let key: K = (k: any);
|
218 | res[key] = merger && base[key] ? merger(base[key], ext[key]) : ext[key];
|
219 | }
|
220 |
|
221 |
|
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 | }
|