@angular-devkit/build-angular 15.0.0-next.5 → 15.0.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -9
- package/src/builders/browser-esbuild/compiler-plugin.d.ts +1 -1
- package/src/builders/browser-esbuild/compiler-plugin.js +4 -2
- package/src/builders/karma/index.d.ts +1 -1
- package/src/builders/karma/index.js +4 -5
- package/src/sass/sass-service.d.ts +12 -1
- package/src/sass/sass-service.js +19 -11
- package/src/webpack/configs/styles.js +43 -36
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-devkit/build-angular",
|
|
3
|
-
"version": "15.0.0-next.
|
|
3
|
+
"version": "15.0.0-next.6",
|
|
4
4
|
"description": "Angular Webpack Build Facade",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"typings": "src/index.d.ts",
|
|
7
7
|
"builders": "builders.json",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@ampproject/remapping": "2.2.0",
|
|
10
|
-
"@angular-devkit/architect": "0.1500.0-next.
|
|
11
|
-
"@angular-devkit/build-webpack": "0.1500.0-next.
|
|
12
|
-
"@angular-devkit/core": "15.0.0-next.
|
|
10
|
+
"@angular-devkit/architect": "0.1500.0-next.6",
|
|
11
|
+
"@angular-devkit/build-webpack": "0.1500.0-next.6",
|
|
12
|
+
"@angular-devkit/core": "15.0.0-next.6",
|
|
13
13
|
"@babel/core": "7.19.3",
|
|
14
14
|
"@babel/generator": "7.19.5",
|
|
15
15
|
"@babel/helper-annotate-as-pure": "7.18.6",
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
"@babel/runtime": "7.19.4",
|
|
21
21
|
"@babel/template": "7.18.10",
|
|
22
22
|
"@discoveryjs/json-ext": "0.5.7",
|
|
23
|
-
"@ngtools/webpack": "15.0.0-next.
|
|
23
|
+
"@ngtools/webpack": "15.0.0-next.6",
|
|
24
24
|
"ansi-colors": "4.1.3",
|
|
25
25
|
"autoprefixer": "10.4.12",
|
|
26
26
|
"babel-loader": "8.2.5",
|
|
27
27
|
"babel-plugin-istanbul": "6.1.1",
|
|
28
28
|
"browserslist": "^4.9.1",
|
|
29
|
-
"cacache": "
|
|
29
|
+
"cacache": "17.0.0",
|
|
30
30
|
"chokidar": "3.5.3",
|
|
31
31
|
"copy-webpack-plugin": "11.0.0",
|
|
32
32
|
"critters": "0.0.16",
|
|
@@ -47,10 +47,9 @@
|
|
|
47
47
|
"ora": "5.4.1",
|
|
48
48
|
"parse5-html-rewriting-stream": "6.0.1",
|
|
49
49
|
"piscina": "3.2.0",
|
|
50
|
-
"postcss": "8.4.
|
|
51
|
-
"postcss-import": "15.0.0",
|
|
50
|
+
"postcss": "8.4.18",
|
|
52
51
|
"postcss-loader": "7.0.1",
|
|
53
|
-
"regenerator-runtime": "0.13.
|
|
52
|
+
"regenerator-runtime": "0.13.10",
|
|
54
53
|
"resolve-url-loader": "5.0.0",
|
|
55
54
|
"rxjs": "6.6.7",
|
|
56
55
|
"sass": "1.55.0",
|
|
@@ -74,6 +73,7 @@
|
|
|
74
73
|
"peerDependencies": {
|
|
75
74
|
"@angular/compiler-cli": "^15.0.0-next",
|
|
76
75
|
"@angular/localize": "^15.0.0-next",
|
|
76
|
+
"@angular/platform-server": "^15.0.0-next",
|
|
77
77
|
"@angular/service-worker": "^15.0.0-next",
|
|
78
78
|
"karma": "^6.3.0",
|
|
79
79
|
"ng-packagr": "^15.0.0-next",
|
|
@@ -85,6 +85,9 @@
|
|
|
85
85
|
"@angular/localize": {
|
|
86
86
|
"optional": true
|
|
87
87
|
},
|
|
88
|
+
"@angular/platform-server": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
88
91
|
"@angular/service-worker": {
|
|
89
92
|
"optional": true
|
|
90
93
|
},
|
|
@@ -10,7 +10,7 @@ import ts from 'typescript';
|
|
|
10
10
|
import { BundleStylesheetOptions } from './stylesheets';
|
|
11
11
|
export declare class SourceFileCache extends Map<string, ts.SourceFile> {
|
|
12
12
|
readonly modifiedFiles: Set<string>;
|
|
13
|
-
readonly babelFileCache: Map<string,
|
|
13
|
+
readonly babelFileCache: Map<string, Uint8Array>;
|
|
14
14
|
invalidate(files: Iterable<string>): void;
|
|
15
15
|
}
|
|
16
16
|
export interface CompilerPluginOptions {
|
|
@@ -349,7 +349,8 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
|
|
|
349
349
|
// would need to be added to the key as well.
|
|
350
350
|
let contents = babelDataCache.get(data);
|
|
351
351
|
if (contents === undefined) {
|
|
352
|
-
|
|
352
|
+
const transformedData = await transformWithBabel(args.path, data, pluginOptions);
|
|
353
|
+
contents = Buffer.from(transformedData, 'utf-8');
|
|
353
354
|
babelDataCache.set(data, contents);
|
|
354
355
|
}
|
|
355
356
|
return {
|
|
@@ -366,7 +367,8 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
|
|
|
366
367
|
let contents = (_a = pluginOptions.sourceFileCache) === null || _a === void 0 ? void 0 : _a.babelFileCache.get(args.path);
|
|
367
368
|
if (contents === undefined) {
|
|
368
369
|
const data = await fs_1.promises.readFile(args.path, 'utf-8');
|
|
369
|
-
|
|
370
|
+
const transformedData = await transformWithBabel(args.path, data, pluginOptions);
|
|
371
|
+
contents = Buffer.from(transformedData, 'utf-8');
|
|
370
372
|
(_b = pluginOptions.sourceFileCache) === null || _b === void 0 ? void 0 : _b.babelFileCache.set(args.path, contents);
|
|
371
373
|
}
|
|
372
374
|
return {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* found in the LICENSE file at https://angular.io/license
|
|
7
7
|
*/
|
|
8
8
|
import { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
|
|
9
|
-
import { ConfigOptions } from 'karma';
|
|
9
|
+
import type { ConfigOptions } from 'karma';
|
|
10
10
|
import { Observable } from 'rxjs';
|
|
11
11
|
import { Configuration } from 'webpack';
|
|
12
12
|
import { ExecutionTransformer } from '../../transforms';
|
|
@@ -33,7 +33,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
33
33
|
exports.execute = void 0;
|
|
34
34
|
const architect_1 = require("@angular-devkit/architect");
|
|
35
35
|
const core_1 = require("@angular-devkit/core");
|
|
36
|
-
const karma_1 = require("karma");
|
|
37
36
|
const module_1 = require("module");
|
|
38
37
|
const path = __importStar(require("path"));
|
|
39
38
|
const rxjs_1 = require("rxjs");
|
|
@@ -91,7 +90,7 @@ function execute(options, context, transforms = {}) {
|
|
|
91
90
|
}
|
|
92
91
|
const karmaOptions = options.karmaConfig
|
|
93
92
|
? {}
|
|
94
|
-
: getBuiltInKarmaConfig(context.workspaceRoot, projectName);
|
|
93
|
+
: getBuiltInKarmaConfig(karma, context.workspaceRoot, projectName);
|
|
95
94
|
karmaOptions.singleRun = singleRun;
|
|
96
95
|
// Convert browsers from a string to an array
|
|
97
96
|
if (options.browsers) {
|
|
@@ -130,7 +129,7 @@ function execute(options, context, transforms = {}) {
|
|
|
130
129
|
webpackConfig,
|
|
131
130
|
logger: context.logger,
|
|
132
131
|
};
|
|
133
|
-
const parsedKarmaConfig = await
|
|
132
|
+
const parsedKarmaConfig = await karma.config.parseConfig(options.karmaConfig && path.resolve(context.workspaceRoot, options.karmaConfig), transforms.karmaOptions ? transforms.karmaOptions(karmaOptions) : karmaOptions, { promiseConfig: true, throwErrors: true });
|
|
134
133
|
return [karma, parsedKarmaConfig];
|
|
135
134
|
}), (0, operators_1.switchMap)(([karma, karmaConfig]) => new rxjs_1.Observable((subscriber) => {
|
|
136
135
|
var _a, _b, _c;
|
|
@@ -154,7 +153,7 @@ function execute(options, context, transforms = {}) {
|
|
|
154
153
|
})), (0, operators_1.defaultIfEmpty)({ success: false }));
|
|
155
154
|
}
|
|
156
155
|
exports.execute = execute;
|
|
157
|
-
function getBuiltInKarmaConfig(workspaceRoot, projectName) {
|
|
156
|
+
function getBuiltInKarmaConfig(karma, workspaceRoot, projectName) {
|
|
158
157
|
let coverageFolderName = projectName.charAt(0) === '@' ? projectName.slice(1) : projectName;
|
|
159
158
|
if (/[A-Z]/.test(coverageFolderName)) {
|
|
160
159
|
coverageFolderName = core_1.strings.dasherize(coverageFolderName);
|
|
@@ -184,7 +183,7 @@ function getBuiltInKarmaConfig(workspaceRoot, projectName) {
|
|
|
184
183
|
reporters: ['progress', 'kjhtml'],
|
|
185
184
|
port: 9876,
|
|
186
185
|
colors: true,
|
|
187
|
-
logLevel:
|
|
186
|
+
logLevel: karma.constants.LOG_INFO,
|
|
188
187
|
autoWatch: true,
|
|
189
188
|
browsers: ['Chrome'],
|
|
190
189
|
restartOnFileChange: true,
|
|
@@ -5,7 +5,17 @@
|
|
|
5
5
|
* Use of this source code is governed by an MIT-style license that can be
|
|
6
6
|
* found in the LICENSE file at https://angular.io/license
|
|
7
7
|
*/
|
|
8
|
-
import { CompileResult, StringOptionsWithImporter, StringOptionsWithoutImporter } from 'sass';
|
|
8
|
+
import { CompileResult, FileImporter, StringOptionsWithImporter, StringOptionsWithoutImporter } from 'sass';
|
|
9
|
+
declare type FileImporterOptions = Parameters<FileImporter['findFileUrl']>[1];
|
|
10
|
+
export interface FileImporterWithRequestContextOptions extends FileImporterOptions {
|
|
11
|
+
/**
|
|
12
|
+
* This is a custom option and is required as SASS does not provide context from which the file is being resolved.
|
|
13
|
+
* This breaks Yarn PNP as transitive deps cannot be resolved from the workspace root.
|
|
14
|
+
*
|
|
15
|
+
* Workaround until https://github.com/sass/sass/issues/3247 is addressed.
|
|
16
|
+
*/
|
|
17
|
+
previousResolvedModules?: Set<string>;
|
|
18
|
+
}
|
|
9
19
|
/**
|
|
10
20
|
* A Sass renderer implementation that provides an interface that can be used by Webpack's
|
|
11
21
|
* `sass-loader`. The implementation uses a Worker thread to perform the Sass rendering
|
|
@@ -45,3 +55,4 @@ export declare class SassWorkerImplementation {
|
|
|
45
55
|
private createRequest;
|
|
46
56
|
private isImporter;
|
|
47
57
|
}
|
|
58
|
+
export {};
|
package/src/sass/sass-service.js
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.SassWorkerImplementation = void 0;
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const node_url_1 = require("node:url");
|
|
13
|
+
const node_worker_threads_1 = require("node:worker_threads");
|
|
14
14
|
const environment_options_1 = require("../utils/environment-options");
|
|
15
15
|
/**
|
|
16
16
|
* The maximum number of Workers that will be created to execute render requests.
|
|
@@ -27,7 +27,7 @@ class SassWorkerImplementation {
|
|
|
27
27
|
this.workers = [];
|
|
28
28
|
this.availableWorkers = [];
|
|
29
29
|
this.requests = new Map();
|
|
30
|
-
this.workerPath = (0,
|
|
30
|
+
this.workerPath = (0, node_path_1.join)(__dirname, './worker.js');
|
|
31
31
|
this.idCounter = 1;
|
|
32
32
|
this.nextWorkerIndex = 0;
|
|
33
33
|
}
|
|
@@ -76,7 +76,7 @@ class SassWorkerImplementation {
|
|
|
76
76
|
if (error) {
|
|
77
77
|
const url = error === null || error === void 0 ? void 0 : error.span.url;
|
|
78
78
|
if (url) {
|
|
79
|
-
error.span.url = (0,
|
|
79
|
+
error.span.url = (0, node_url_1.pathToFileURL)(url);
|
|
80
80
|
}
|
|
81
81
|
reject(error);
|
|
82
82
|
return;
|
|
@@ -96,7 +96,7 @@ class SassWorkerImplementation {
|
|
|
96
96
|
options: {
|
|
97
97
|
...serializableOptions,
|
|
98
98
|
// URL is not serializable so to convert to string here and back to URL in the worker.
|
|
99
|
-
url: url ? (0,
|
|
99
|
+
url: url ? (0, node_url_1.fileURLToPath)(url) : undefined,
|
|
100
100
|
},
|
|
101
101
|
});
|
|
102
102
|
});
|
|
@@ -115,9 +115,9 @@ class SassWorkerImplementation {
|
|
|
115
115
|
this.requests.clear();
|
|
116
116
|
}
|
|
117
117
|
createWorker() {
|
|
118
|
-
const { port1: mainImporterPort, port2: workerImporterPort } = new
|
|
118
|
+
const { port1: mainImporterPort, port2: workerImporterPort } = new node_worker_threads_1.MessageChannel();
|
|
119
119
|
const importerSignal = new Int32Array(new SharedArrayBuffer(4));
|
|
120
|
-
const worker = new
|
|
120
|
+
const worker = new node_worker_threads_1.Worker(this.workerPath, {
|
|
121
121
|
workerData: { workerImporterPort, importerSignal },
|
|
122
122
|
transferList: [workerImporterPort],
|
|
123
123
|
});
|
|
@@ -132,7 +132,7 @@ class SassWorkerImplementation {
|
|
|
132
132
|
request.callback(undefined, {
|
|
133
133
|
...response.result,
|
|
134
134
|
// URL is not serializable so in the worker we convert to string and here back to URL.
|
|
135
|
-
loadedUrls: response.result.loadedUrls.map((p) => (0,
|
|
135
|
+
loadedUrls: response.result.loadedUrls.map((p) => (0, node_url_1.pathToFileURL)(p)),
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
else {
|
|
@@ -147,8 +147,16 @@ class SassWorkerImplementation {
|
|
|
147
147
|
Atomics.notify(importerSignal, 0);
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
|
-
this.processImporters(request.importers, url,
|
|
150
|
+
this.processImporters(request.importers, url, {
|
|
151
|
+
...options,
|
|
152
|
+
previousResolvedModules: request.previousResolvedModules,
|
|
153
|
+
})
|
|
151
154
|
.then((result) => {
|
|
155
|
+
var _a;
|
|
156
|
+
if (result) {
|
|
157
|
+
(_a = request.previousResolvedModules) !== null && _a !== void 0 ? _a : (request.previousResolvedModules = new Set());
|
|
158
|
+
request.previousResolvedModules.add((0, node_path_1.dirname)(result));
|
|
159
|
+
}
|
|
152
160
|
mainImporterPort.postMessage(result);
|
|
153
161
|
})
|
|
154
162
|
.catch((error) => {
|
|
@@ -171,7 +179,7 @@ class SassWorkerImplementation {
|
|
|
171
179
|
// File importer (Can be sync or aync).
|
|
172
180
|
const result = await importer.findFileUrl(url, options);
|
|
173
181
|
if (result) {
|
|
174
|
-
return (0,
|
|
182
|
+
return (0, node_url_1.fileURLToPath)(result);
|
|
175
183
|
}
|
|
176
184
|
}
|
|
177
185
|
return null;
|
|
@@ -34,10 +34,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
34
34
|
};
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.getStylesConfig = void 0;
|
|
37
|
-
const fs = __importStar(require("fs"));
|
|
38
37
|
const mini_css_extract_plugin_1 = __importDefault(require("mini-css-extract-plugin"));
|
|
39
|
-
const
|
|
40
|
-
const
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const node_url_1 = require("node:url");
|
|
41
41
|
const sass_service_1 = require("../../sass/sass-service");
|
|
42
42
|
const sass_service_legacy_1 = require("../../sass/sass-service-legacy");
|
|
43
43
|
const environment_options_1 = require("../../utils/environment-options");
|
|
@@ -48,7 +48,7 @@ const helpers_1 = require("../utils/helpers");
|
|
|
48
48
|
// eslint-disable-next-line max-lines-per-function
|
|
49
49
|
function getStylesConfig(wco) {
|
|
50
50
|
var _a, _b, _c;
|
|
51
|
-
const { root,
|
|
51
|
+
const { root, buildOptions } = wco;
|
|
52
52
|
const extraPlugins = [];
|
|
53
53
|
extraPlugins.push(new plugins_1.AnyComponentStyleBudgetChecker(buildOptions.budgets));
|
|
54
54
|
const cssSourceMap = buildOptions.sourceMap.styles;
|
|
@@ -101,10 +101,8 @@ function getStylesConfig(wco) {
|
|
|
101
101
|
extraPostcssPlugins.push(require(tailwindPackagePath)({ config: tailwindConfigPath }));
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
const postcssImports = require('postcss-import');
|
|
105
104
|
const autoprefixer = require('autoprefixer');
|
|
106
105
|
const postcssOptionsCreator = (inlineSourcemaps, extracted) => {
|
|
107
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
106
|
const optionGenerator = (loader) => ({
|
|
109
107
|
map: inlineSourcemaps
|
|
110
108
|
? {
|
|
@@ -113,20 +111,6 @@ function getStylesConfig(wco) {
|
|
|
113
111
|
}
|
|
114
112
|
: undefined,
|
|
115
113
|
plugins: [
|
|
116
|
-
postcssImports({
|
|
117
|
-
load: (filename) => {
|
|
118
|
-
return new Promise((resolve, reject) => {
|
|
119
|
-
loader.fs.readFile(filename, (err, data) => {
|
|
120
|
-
if (err) {
|
|
121
|
-
reject(err);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
const content = data.toString();
|
|
125
|
-
resolve(content);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
},
|
|
129
|
-
}),
|
|
130
114
|
(0, plugins_1.PostcssCliResources)({
|
|
131
115
|
baseHref: buildOptions.baseHref,
|
|
132
116
|
deployUrl: buildOptions.deployUrl,
|
|
@@ -165,6 +149,16 @@ function getStylesConfig(wco) {
|
|
|
165
149
|
const postCss = require('postcss');
|
|
166
150
|
const postCssLoaderPath = require.resolve('postcss-loader');
|
|
167
151
|
const componentStyleLoaders = [
|
|
152
|
+
{
|
|
153
|
+
loader: require.resolve('css-loader'),
|
|
154
|
+
options: {
|
|
155
|
+
url: false,
|
|
156
|
+
sourceMap: componentsSourceMap,
|
|
157
|
+
importLoaders: 1,
|
|
158
|
+
exportType: 'string',
|
|
159
|
+
esModule: false,
|
|
160
|
+
},
|
|
161
|
+
},
|
|
168
162
|
{
|
|
169
163
|
loader: postCssLoaderPath,
|
|
170
164
|
options: {
|
|
@@ -182,6 +176,7 @@ function getStylesConfig(wco) {
|
|
|
182
176
|
options: {
|
|
183
177
|
url: false,
|
|
184
178
|
sourceMap: !!cssSourceMap,
|
|
179
|
+
importLoaders: 1,
|
|
185
180
|
},
|
|
186
181
|
},
|
|
187
182
|
{
|
|
@@ -261,7 +256,6 @@ function getStylesConfig(wco) {
|
|
|
261
256
|
// Component styles are all styles except defined global styles
|
|
262
257
|
{
|
|
263
258
|
use: componentStyleLoaders,
|
|
264
|
-
type: 'asset/source',
|
|
265
259
|
resourceQuery: /\?ngResource/,
|
|
266
260
|
},
|
|
267
261
|
],
|
|
@@ -370,28 +364,41 @@ function getSassResolutionImporter(loaderContext, root, preserveSymlinks) {
|
|
|
370
364
|
mainFiles: ['_index', 'index', '...'],
|
|
371
365
|
});
|
|
372
366
|
return {
|
|
373
|
-
findFileUrl: async (url, { fromImport }) => {
|
|
367
|
+
findFileUrl: async (url, { fromImport, previousResolvedModules }) => {
|
|
374
368
|
if (url.charAt(0) === '.') {
|
|
375
369
|
// Let Sass handle relative imports.
|
|
376
370
|
return null;
|
|
377
371
|
}
|
|
378
|
-
let file;
|
|
379
372
|
const resolve = fromImport ? resolveImport : resolveModule;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (underscoreIndex > 0 && url.charAt(underscoreIndex) !== '_') {
|
|
390
|
-
const partialFileUrl = `${url.slice(0, underscoreIndex)}_${url.slice(underscoreIndex)}`;
|
|
391
|
-
file = await resolve(root, partialFileUrl).catch(() => undefined);
|
|
373
|
+
// Try to resolve from root of workspace
|
|
374
|
+
let result = await tryResolve(resolve, root, url);
|
|
375
|
+
// Try to resolve from previously resolved modules.
|
|
376
|
+
if (!result && previousResolvedModules) {
|
|
377
|
+
for (const path of previousResolvedModules) {
|
|
378
|
+
result = await tryResolve(resolve, path, url);
|
|
379
|
+
if (result) {
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
392
382
|
}
|
|
393
383
|
}
|
|
394
|
-
return
|
|
384
|
+
return result ? (0, node_url_1.pathToFileURL)(result) : null;
|
|
395
385
|
},
|
|
396
386
|
};
|
|
397
387
|
}
|
|
388
|
+
async function tryResolve(resolve, root, url) {
|
|
389
|
+
try {
|
|
390
|
+
return await resolve(root, url);
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
// Try to resolve a partial file
|
|
394
|
+
// @use '@material/button/button' as mdc-button;
|
|
395
|
+
// `@material/button/button` -> `@material/button/_button`
|
|
396
|
+
const lastSlashIndex = url.lastIndexOf('/');
|
|
397
|
+
const underscoreIndex = lastSlashIndex + 1;
|
|
398
|
+
if (underscoreIndex > 0 && url.charAt(underscoreIndex) !== '_') {
|
|
399
|
+
const partialFileUrl = `${url.slice(0, underscoreIndex)}_${url.slice(underscoreIndex)}`;
|
|
400
|
+
return resolve(root, partialFileUrl).catch(() => undefined);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|