1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | const fs = require('fs');
|
12 | const path = require('path');
|
13 |
|
14 | const webpack = require('webpack');
|
15 | const TerserPlugin = require('terser-webpack-plugin');
|
16 | const PnpWebpackPlugin = require('pnp-webpack-plugin');
|
17 | const ProgressBarPlugin = require('progress-bar-webpack-plugin');
|
18 | const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
|
19 | const DefaultNoImportSideEffectsPlugin = require('default-no-import-side-effects-webpack-plugin');
|
20 | const ChunkIdPrefixPlugin = require('./plugins/chunk-id-prefix-plugin.js');
|
21 | const {
|
22 | gzipWebpackPlugin,
|
23 | brotliWebpackPlugin,
|
24 | svgoWebpackPlugin,
|
25 | } = require('../lib/compression');
|
26 | const resolveFrom = require('../lib/resolve-from');
|
27 | const LoaderContextProviderPlugin = require('./plugins/loader-context-provider-plugin.js');
|
28 | const ChildCompilationPlugin = require('./plugins/child-compilation-plugin.js');
|
29 | const {
|
30 | chunkIdsLoader,
|
31 | fileLoader,
|
32 | babelLoader,
|
33 | i18nManifestLoader,
|
34 | chunkUrlMapLoader,
|
35 | syncChunkIdsLoader,
|
36 | syncChunkPathsLoader,
|
37 | swLoader,
|
38 | workerLoader,
|
39 | } = require('./loaders/index.js');
|
40 | const {
|
41 | translationsManifestContextKey,
|
42 | clientChunkMetadataContextKey,
|
43 | devContextKey,
|
44 | workerKey,
|
45 | } = require('./loaders/loader-context.js');
|
46 | const ClientChunkMetadataStateHydratorPlugin = require('./plugins/client-chunk-metadata-state-hydrator-plugin.js');
|
47 | const InstrumentedImportDependencyTemplatePlugin = require('./plugins/instrumented-import-dependency-template-plugin');
|
48 | const I18nDiscoveryPlugin = require('./plugins/i18n-discovery-plugin.js');
|
49 | const SourceMapPlugin = require('./plugins/source-map-plugin.js');
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | const COMPILATIONS = {
|
56 | server: 'server',
|
57 | serverless: 'server',
|
58 | 'client-modern': 'client',
|
59 | sw: 'sw',
|
60 | };
|
61 | const EXCLUDE_TRANSPILATION_PATTERNS = [
|
62 | /node_modules\/mapbox-gl\
|
63 | /node_modules\/react-dom\
|
64 | /node_modules\/react\
|
65 | /node_modules\/core-js\
|
66 | ];
|
67 | const JS_EXT_PATTERN = /\.(mjs|js|jsx)$/;
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 | const isProjectCode = (modulePath /*:string*/, dir /*:string*/) =>
|
109 | modulePath.startsWith(getSrcPath(dir)) ||
|
110 | /fusion-cli(\/|\\)(entries|plugins)/.test(modulePath);
|
111 |
|
112 | const getTransformDefault = (modulePath /*:string*/, dir /*:string*/) =>
|
113 | isProjectCode(modulePath, dir) ? 'all' : 'spec';
|
114 | module.exports = {
|
115 | getWebpackConfig,
|
116 | getTransformDefault,
|
117 | };
|
118 |
|
119 | function getWebpackConfig(opts /*: WebpackConfigOpts */) {
|
120 | const {
|
121 | id,
|
122 | dev,
|
123 | dir,
|
124 | hmr,
|
125 | watch,
|
126 | state,
|
127 | fusionConfig,
|
128 | zopfli,
|
129 | gzip,
|
130 | brotli,
|
131 | minify,
|
132 | skipSourceMaps,
|
133 | legacyPkgConfig = {},
|
134 | worker,
|
135 | } = opts;
|
136 | const main = 'src/main.js';
|
137 |
|
138 | if (!fs.existsSync(path.join(dir, main))) {
|
139 | throw new Error(`Project directory must contain a ${main} file`);
|
140 | }
|
141 |
|
142 | const runtime = COMPILATIONS[id];
|
143 | const env = dev ? 'development' : 'production';
|
144 | const shouldMinify = !dev && minify;
|
145 |
|
146 |
|
147 |
|
148 | const shouldGzip = zopfli && gzip;
|
149 |
|
150 | const babelConfigData = {
|
151 | target: runtime === 'server' ? 'node-bundled' : 'browser-modern',
|
152 | specOnly: true,
|
153 | plugins:
|
154 | fusionConfig.babel && fusionConfig.babel.plugins
|
155 | ? fusionConfig.babel.plugins
|
156 | : [],
|
157 | presets:
|
158 | fusionConfig.babel && fusionConfig.babel.presets
|
159 | ? fusionConfig.babel.presets
|
160 | : [],
|
161 | };
|
162 |
|
163 | const babelOverridesData = {
|
164 | dev: dev,
|
165 | fusionTransforms: true,
|
166 | assumeNoImportSideEffects: fusionConfig.assumeNoImportSideEffects,
|
167 | target: runtime === 'server' ? 'node-bundled' : 'browser-modern',
|
168 | specOnly: false,
|
169 | };
|
170 |
|
171 | const legacyBabelOverridesData = {
|
172 | dev: dev,
|
173 | fusionTransforms: true,
|
174 | assumeNoImportSideEffects: fusionConfig.assumeNoImportSideEffects,
|
175 | target: runtime === 'server' ? 'node-bundled' : 'browser-legacy',
|
176 | specOnly: false,
|
177 | };
|
178 |
|
179 | const {experimentalBundleTest, experimentalTransformTest} = fusionConfig;
|
180 | const babelTester = experimentalTransformTest
|
181 | ? modulePath => {
|
182 | if (!JS_EXT_PATTERN.test(modulePath)) {
|
183 | return false;
|
184 | }
|
185 | const transform = experimentalTransformTest(
|
186 | modulePath,
|
187 | getTransformDefault(modulePath, dir)
|
188 | );
|
189 | if (transform === 'none') {
|
190 | return false;
|
191 | } else if (transform === 'all' || transform === 'spec') {
|
192 | return true;
|
193 | } else {
|
194 | throw new Error(
|
195 | `Unexpected value from experimentalTransformTest ${transform}. Expected 'spec' | 'all' | 'none'`
|
196 | );
|
197 | }
|
198 | }
|
199 | : JS_EXT_PATTERN;
|
200 |
|
201 | return {
|
202 | name: runtime,
|
203 | target: {server: 'node', client: 'web', sw: 'webworker'}[runtime],
|
204 | entry: {
|
205 | main: [
|
206 | runtime === 'client' &&
|
207 | path.join(__dirname, '../entries/client-public-path.js'),
|
208 | runtime === 'server' &&
|
209 | path.join(__dirname, '../entries/server-public-path.js'),
|
210 | dev &&
|
211 | hmr &&
|
212 | watch &&
|
213 | runtime !== 'server' &&
|
214 | `${require.resolve('webpack-hot-middleware/client')}?name=client`,
|
215 |
|
216 | dev &&
|
217 | hmr &&
|
218 | watch &&
|
219 | runtime === 'server' &&
|
220 | `${require.resolve('webpack/hot/poll')}?1000`,
|
221 | runtime === 'server' &&
|
222 | path.join(__dirname, `../entries/${id}-entry.js`),
|
223 | runtime === 'client' &&
|
224 | path.join(__dirname, '../entries/client-entry.js'),
|
225 | ].filter(Boolean),
|
226 | },
|
227 | mode: dev ? 'development' : 'production',
|
228 |
|
229 | stats: 'minimal',
|
230 | |
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 | devtool: skipSourceMaps
|
242 | ? false
|
243 | : runtime === 'client' && !dev
|
244 | ? 'source-map'
|
245 | : runtime === 'sw'
|
246 | ? 'hidden-source-map'
|
247 | : 'cheap-module-source-map',
|
248 | output: {
|
249 | path: path.join(dir, `.fusion/dist/${env}/${runtime}`),
|
250 | filename:
|
251 | runtime === 'server'
|
252 | ? 'server-main.js'
|
253 | : dev
|
254 | ? 'client-[name].js'
|
255 | : 'client-[name]-[chunkhash].js',
|
256 | libraryTarget: runtime === 'server' ? 'commonjs2' : 'var',
|
257 |
|
258 |
|
259 | sourceMapFilename: `[file].map`,
|
260 |
|
261 | publicPath: void 0,
|
262 | crossOriginLoading: 'anonymous',
|
263 | devtoolModuleFilenameTemplate: (info /*: Object */) => {
|
264 |
|
265 | return path.isAbsolute(info.absoluteResourcePath)
|
266 | ? info.absoluteResourcePath
|
267 | : path.join(dir, info.absoluteResourcePath);
|
268 | },
|
269 | },
|
270 | performance: {
|
271 | hints: false,
|
272 | },
|
273 | context: dir,
|
274 | node: Object.assign(
|
275 | getNodeConfig(runtime),
|
276 | legacyPkgConfig.node,
|
277 | fusionConfig.nodeBuiltins
|
278 | ),
|
279 | module: {
|
280 | |
281 |
|
282 |
|
283 |
|
284 | strictExportPresence: true,
|
285 | rules: [
|
286 | |
287 |
|
288 |
|
289 | runtime === 'server' && {
|
290 | compiler: id => id === 'server',
|
291 | test: babelTester,
|
292 | exclude: EXCLUDE_TRANSPILATION_PATTERNS,
|
293 | use: [
|
294 | {
|
295 | loader: babelLoader.path,
|
296 | options: {
|
297 | dir,
|
298 | configCacheKey: 'server-config',
|
299 | overrideCacheKey: 'server-override',
|
300 | babelConfigData: {...babelConfigData},
|
301 | |
302 |
|
303 |
|
304 | overrides: [
|
305 | {
|
306 | ...babelOverridesData,
|
307 | },
|
308 | ],
|
309 | },
|
310 | },
|
311 | ],
|
312 | },
|
313 | |
314 |
|
315 |
|
316 | (runtime === 'client' || runtime === 'sw') && {
|
317 | compiler: id => id === 'client' || id === 'sw',
|
318 | test: babelTester,
|
319 | exclude: EXCLUDE_TRANSPILATION_PATTERNS,
|
320 | use: [
|
321 | {
|
322 | loader: babelLoader.path,
|
323 | options: {
|
324 | dir,
|
325 | configCacheKey: 'client-config',
|
326 | overrideCacheKey: 'client-override',
|
327 | babelConfigData: {...babelConfigData},
|
328 | |
329 |
|
330 |
|
331 | overrides: [
|
332 | {
|
333 | ...babelOverridesData,
|
334 | },
|
335 | ],
|
336 | },
|
337 | },
|
338 | ],
|
339 | },
|
340 | |
341 |
|
342 |
|
343 | runtime === 'client' && {
|
344 | compiler: id => id === 'client-legacy',
|
345 | test: babelTester,
|
346 | exclude: EXCLUDE_TRANSPILATION_PATTERNS,
|
347 | use: [
|
348 | {
|
349 | loader: babelLoader.path,
|
350 | options: {
|
351 | dir,
|
352 | configCacheKey: 'legacy-config',
|
353 | overrideCacheKey: 'legacy-override',
|
354 | babelConfigData: {
|
355 | target:
|
356 | runtime === 'server' ? 'node-bundled' : 'browser-legacy',
|
357 | specOnly: true,
|
358 | plugins:
|
359 | fusionConfig.babel && fusionConfig.babel.plugins
|
360 | ? fusionConfig.babel.plugins
|
361 | : [],
|
362 | presets:
|
363 | fusionConfig.babel && fusionConfig.babel.presets
|
364 | ? fusionConfig.babel.presets
|
365 | : [],
|
366 | },
|
367 | |
368 |
|
369 |
|
370 | overrides: [
|
371 | {
|
372 | ...legacyBabelOverridesData,
|
373 | },
|
374 | ],
|
375 | },
|
376 | },
|
377 | ],
|
378 | },
|
379 | {
|
380 | test: /\.json$/,
|
381 | type: 'javascript/auto',
|
382 | loader: require.resolve('./loaders/json-loader.js'),
|
383 | },
|
384 | {
|
385 | test: /\.ya?ml$/,
|
386 | type: 'json',
|
387 | loader: require.resolve('yaml-loader'),
|
388 | },
|
389 | {
|
390 | test: /\.graphql$|.gql$/,
|
391 | loader: require.resolve('graphql-tag/loader'),
|
392 | },
|
393 | fusionConfig.assumeNoImportSideEffects && {
|
394 | sideEffects: false,
|
395 | test: modulePath => {
|
396 | if (
|
397 | modulePath.includes('core-js/modules') ||
|
398 | modulePath.includes('regenerator-runtime/runtime')
|
399 | ) {
|
400 | return false;
|
401 | }
|
402 |
|
403 | return true;
|
404 | },
|
405 | },
|
406 | ].filter(Boolean),
|
407 | },
|
408 | externals: [
|
409 | runtime === 'server' &&
|
410 | ((context, request, callback) => {
|
411 | if (/^[@a-z\-0-9]+/.test(request)) {
|
412 | const absolutePath = resolveFrom.silent(context, request);
|
413 |
|
414 | if (typeof absolutePath !== 'string') {
|
415 |
|
416 | return callback(null, request);
|
417 | }
|
418 | if (experimentalBundleTest) {
|
419 | const bundle = experimentalBundleTest(
|
420 | absolutePath,
|
421 | 'browser-only'
|
422 | );
|
423 | if (bundle === 'browser-only') {
|
424 |
|
425 | return callback(null, 'commonjs ' + absolutePath);
|
426 | } else if (bundle === 'universal') {
|
427 |
|
428 | return callback();
|
429 | } else {
|
430 | throw new Error(
|
431 | `Unexpected value: ${bundle} from experimentalBundleTest. Expected 'browser-only' | 'universal'.`
|
432 | );
|
433 | }
|
434 | }
|
435 | return callback(null, 'commonjs ' + absolutePath);
|
436 | }
|
437 |
|
438 | return callback();
|
439 | }),
|
440 | ].filter(Boolean),
|
441 | resolve: {
|
442 | symlinks: process.env.NODE_PRESERVE_SYMLINKS ? false : true,
|
443 | aliasFields: [
|
444 | (runtime === 'client' || runtime === 'sw') && 'browser',
|
445 | 'es2015',
|
446 | 'es2017',
|
447 | ].filter(Boolean),
|
448 | alias: {
|
449 |
|
450 | __FUSION_ENTRY_PATH__: path.join(dir, main),
|
451 | __ENV__: env,
|
452 | },
|
453 | plugins: [PnpWebpackPlugin],
|
454 | },
|
455 | resolveLoader: {
|
456 | symlinks: process.env.NODE_PRESERVE_SYMLINKS ? false : true,
|
457 | alias: {
|
458 | [fileLoader.alias]: fileLoader.path,
|
459 | [chunkIdsLoader.alias]: chunkIdsLoader.path,
|
460 | [syncChunkIdsLoader.alias]: syncChunkIdsLoader.path,
|
461 | [syncChunkPathsLoader.alias]: syncChunkPathsLoader.path,
|
462 | [chunkUrlMapLoader.alias]: chunkUrlMapLoader.path,
|
463 | [i18nManifestLoader.alias]: i18nManifestLoader.path,
|
464 | [swLoader.alias]: swLoader.path,
|
465 | [workerLoader.alias]: workerLoader.path,
|
466 | },
|
467 | plugins: [PnpWebpackPlugin.moduleLoader(module)],
|
468 | },
|
469 |
|
470 | plugins: [
|
471 | runtime === 'client' && !dev && new SourceMapPlugin(),
|
472 | runtime === 'client' &&
|
473 | new webpack.optimize.RuntimeChunkPlugin({
|
474 | name: 'runtime',
|
475 | }),
|
476 | (fusionConfig.defaultImportSideEffects === false ||
|
477 | Array.isArray(fusionConfig.defaultImportSideEffects)) &&
|
478 | new DefaultNoImportSideEffectsPlugin(
|
479 | Array.isArray(fusionConfig.defaultImportSideEffects)
|
480 | ? {ignoredPackages: fusionConfig.defaultImportSideEffects}
|
481 | : {}
|
482 | ),
|
483 | new webpack.optimize.SideEffectsFlagPlugin(),
|
484 | runtime === 'server' &&
|
485 | new webpack.optimize.LimitChunkCountPlugin({maxChunks: 1}),
|
486 | new ProgressBarPlugin(),
|
487 | runtime === 'server' &&
|
488 | new LoaderContextProviderPlugin('optsContext', opts),
|
489 | new LoaderContextProviderPlugin(devContextKey, dev),
|
490 | runtime === 'server' &&
|
491 | new LoaderContextProviderPlugin(
|
492 | clientChunkMetadataContextKey,
|
493 | state.mergedClientChunkMetadata
|
494 | ),
|
495 | runtime === 'client'
|
496 | ? new I18nDiscoveryPlugin(
|
497 | state.i18nDeferredManifest,
|
498 | state.i18nManifest
|
499 | )
|
500 | : new LoaderContextProviderPlugin(
|
501 | translationsManifestContextKey,
|
502 | state.i18nDeferredManifest
|
503 | ),
|
504 | new LoaderContextProviderPlugin(workerKey, worker),
|
505 | !dev && shouldGzip && gzipWebpackPlugin,
|
506 | !dev && brotli && brotliWebpackPlugin,
|
507 | !dev && svgoWebpackPlugin,
|
508 |
|
509 |
|
510 |
|
511 |
|
512 |
|
513 | watch && new webpack.NoEmitOnErrorsPlugin(),
|
514 | runtime === 'server'
|
515 | ?
|
516 | new InstrumentedImportDependencyTemplatePlugin({
|
517 | compilation: 'server',
|
518 | clientChunkMetadata: state.mergedClientChunkMetadata,
|
519 | })
|
520 | : |
521 |
|
522 |
|
523 |
|
524 |
|
525 | new InstrumentedImportDependencyTemplatePlugin({
|
526 | compilation: 'client',
|
527 | i18nManifest: state.i18nManifest,
|
528 | }),
|
529 | dev && hmr && watch && new webpack.HotModuleReplacementPlugin(),
|
530 | !dev && runtime === 'client' && new webpack.HashedModuleIdsPlugin(),
|
531 | runtime === 'client' &&
|
532 |
|
533 | new CaseSensitivePathsPlugin(),
|
534 | runtime === 'server' &&
|
535 | new webpack.BannerPlugin({
|
536 | raw: true,
|
537 | entryOnly: true,
|
538 |
|
539 | banner: getEnvBanner(env),
|
540 | }),
|
541 | new webpack.EnvironmentPlugin({NODE_ENV: env}),
|
542 | id === 'client-modern' &&
|
543 | new ClientChunkMetadataStateHydratorPlugin(state.clientChunkMetadata),
|
544 | id === 'client-modern' &&
|
545 | new ChildCompilationPlugin({
|
546 | name: 'client-legacy',
|
547 | entry: [
|
548 | path.resolve(__dirname, '../entries/client-public-path.js'),
|
549 | path.resolve(__dirname, '../entries/client-entry.js'),
|
550 |
|
551 | ],
|
552 | enabledState: opts.state.legacyBuildEnabled,
|
553 | outputOptions: {
|
554 | filename: opts.dev
|
555 | ? 'client-legacy-[name].js'
|
556 | : 'client-legacy-[name]-[chunkhash].js',
|
557 | chunkFilename: opts.dev
|
558 | ? 'client-legacy-[name].js'
|
559 | : 'client-legacy-[name]-[chunkhash].js',
|
560 | },
|
561 | plugins: options => [
|
562 | new webpack.optimize.RuntimeChunkPlugin(
|
563 | options.optimization.runtimeChunk
|
564 | ),
|
565 | new webpack.optimize.SplitChunksPlugin(
|
566 | options.optimization.splitChunks
|
567 | ),
|
568 |
|
569 | new InstrumentedImportDependencyTemplatePlugin({
|
570 | compilation: 'client',
|
571 | i18nManifest: state.i18nManifest,
|
572 | }),
|
573 | new ClientChunkMetadataStateHydratorPlugin(
|
574 | state.legacyClientChunkMetadata
|
575 | ),
|
576 | new ChunkIdPrefixPlugin(),
|
577 | ],
|
578 | }),
|
579 | ].filter(Boolean),
|
580 | optimization: {
|
581 | runtimeChunk: runtime === 'client' && {name: 'runtime'},
|
582 | splitChunks:
|
583 | runtime !== 'client'
|
584 | ? void 0
|
585 | : fusionConfig.splitChunks
|
586 | ?
|
587 |
|
588 | {...fusionConfig.splitChunks, automaticNameDelimiter: '-'}
|
589 | : {
|
590 | chunks: 'async',
|
591 | automaticNameDelimiter: '-',
|
592 | cacheGroups: {
|
593 | default: {
|
594 | minChunks: 2,
|
595 | reuseExistingChunk: true,
|
596 | },
|
597 | vendor: {
|
598 | test: /[\\/]node_modules[\\/]/,
|
599 | name: 'vendor',
|
600 | chunks: 'initial',
|
601 | enforce: true,
|
602 | },
|
603 | },
|
604 | },
|
605 | minimize: shouldMinify,
|
606 | minimizer: shouldMinify
|
607 | ? [
|
608 | new TerserPlugin({
|
609 | sourceMap: skipSourceMaps ? false : true,
|
610 | cache: true,
|
611 | parallel: true,
|
612 | extractComments: false,
|
613 | terserOptions: {
|
614 | compress: {
|
615 |
|
616 |
|
617 |
|
618 | typeofs: false,
|
619 |
|
620 |
|
621 |
|
622 | inline: 1,
|
623 | },
|
624 |
|
625 | keep_fnames: opts.preserveNames,
|
626 | keep_classnames: opts.preserveNames,
|
627 | },
|
628 | }),
|
629 | ]
|
630 | : undefined,
|
631 | },
|
632 | };
|
633 | }
|
634 |
|
635 |
|
636 | function getEnvBanner(env) {
|
637 | return `
|
638 | if (process.env.NODE_ENV && process.env.NODE_ENV !== '${env}') {
|
639 | if (${env === 'production' ? 'true' : 'false'}) {
|
640 | throw new Error(\`NODE_ENV (\${process.env.NODE_ENV}) does not match value for compiled assets: ${env}\`);
|
641 | } else {
|
642 | console.warn('Overriding NODE_ENV: ' + process.env.NODE_ENV + ' to ${env} in order to match value for compiled assets');
|
643 | process.env.NODE_ENV = '${env}';
|
644 | }
|
645 | } else {
|
646 | process.env.NODE_ENV = '${env}';
|
647 | }
|
648 | `;
|
649 | }
|
650 |
|
651 | function getNodeConfig(runtime) {
|
652 | const emptyForWeb = runtime === 'client' ? 'empty' : false;
|
653 | return {
|
654 |
|
655 | process: false,
|
656 |
|
657 | Buffer: false,
|
658 |
|
659 | setImmediate: false,
|
660 |
|
661 |
|
662 | __filename: true,
|
663 | __dirname: true,
|
664 |
|
665 | child_process: emptyForWeb,
|
666 | cluster: emptyForWeb,
|
667 | crypto: emptyForWeb,
|
668 | dgram: emptyForWeb,
|
669 | dns: emptyForWeb,
|
670 | fs: emptyForWeb,
|
671 | module: emptyForWeb,
|
672 | net: emptyForWeb,
|
673 | readline: emptyForWeb,
|
674 | repl: emptyForWeb,
|
675 | tls: emptyForWeb,
|
676 | };
|
677 | }
|
678 |
|
679 | function getSrcPath(dir) {
|
680 |
|
681 | if (process.env.NODE_PRESERVE_SYMLINKS) {
|
682 | return path.resolve(dir, 'src');
|
683 | }
|
684 | try {
|
685 | const real = path.dirname(
|
686 | fs.realpathSync(path.resolve(dir, 'package.json'))
|
687 | );
|
688 | return path.resolve(real, 'src');
|
689 | } catch (e) {
|
690 | return path.resolve(dir, 'src');
|
691 | }
|
692 | }
|