Skip to content

Commit cd5849c

Browse files
authoredJul 14, 2023
fix(output-targets): fix path normalization logic (#4545)
In #4317 as part of removing in-browser compilation support we removed the polyfills for nodejs built-in modules like `path` which were injected by Rollup during build-time. Although the _main_ purpose of these polyfills was allowing Stencil to run in the browser in the case of the `path` module there was also a secondary purpose which was ensuring that paths were treated the same way across supported platforms (posix + windows). See, for instance, the following lines in the polyfill: https://github.com/ionic-team/stencil/blob/b911f1986a0d583bd1e3cd42cbbca9b255c32f2d/src/compiler/sys/modules/path.ts#L35-L38 These functions basically wrapped the existing path implementation with our `normalizePath` helper, which would ensure that the output paths would be the same on both windows and posix systems (e.g. macOS and linux). When we merged #4317 an effort was made to add `normalizePath` around the codebase where it was thought that various paths being calculated needed to be platform-independent, however, a few locations were missed (in particular, some paths output into typedefs, which would show up as non-posix paths when building on windows). To address the issue we introduce `relative` and `join` functions into the existing path-related `utils` file (which is incidentally renamed) which work similarly to how the patched functions in the old polyfill did. Then several usage sites are changed to import those new utils instead of the 'raw' functions from `path`. Together these changes should ensure that Stencil's output is not platform-dependent. See here for an issue reporting the problem: #4543
1 parent 445fc4f commit cd5849c

21 files changed

+96
-59
lines changed
 

‎src/compiler/build/build-finish.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { isFunction, isRemoteUrl } from '@utils';
2-
import { relative } from 'path';
1+
import { isFunction, isRemoteUrl, relative } from '@utils';
32

43
import type * as d from '../../declarations';
54
import { generateBuildResults } from './build-results';

‎src/compiler/output-targets/dist-collection/index.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import {
44
flatOne,
55
generatePreamble,
66
isOutputTargetDistCollection,
7+
join,
78
normalizePath,
9+
relative,
810
sortBy,
911
} from '@utils';
10-
import { join, relative } from 'path';
1112
import ts from 'typescript';
1213

1314
import type * as d from '../../../declarations';
@@ -108,10 +109,10 @@ const writeCollectionManifest = async (
108109
outputTarget: d.OutputTargetDistCollection
109110
) => {
110111
// get the absolute path to the directory where the collection will be saved
111-
const collectionDir = normalizePath(outputTarget.collectionDir);
112+
const { collectionDir } = outputTarget;
112113

113114
// create an absolute file path to the actual collection json file
114-
const collectionFilePath = normalizePath(join(collectionDir, COLLECTION_MANIFEST_FILE_NAME));
115+
const collectionFilePath = join(collectionDir, COLLECTION_MANIFEST_FILE_NAME);
115116

116117
// don't bother serializing/writing the collection if we're not creating a distribution
117118
await compilerCtx.fs.writeFile(collectionFilePath, collectionData);

‎src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { dashToPascalCase, isOutputTargetDistCustomElements, normalizePath } from '@utils';
2-
import { dirname, join, relative } from 'path';
1+
import { dashToPascalCase, isOutputTargetDistCustomElements, join, normalizePath, relative } from '@utils';
2+
import { dirname } from 'path';
33

44
import type * as d from '../../../declarations';
55

‎src/compiler/output-targets/output-lazy-loader.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { generatePreamble, isOutputTargetDistLazyLoader, normalizePath, relativeImport } from '@utils';
2-
import { join, relative } from 'path';
1+
import { generatePreamble, isOutputTargetDistLazyLoader, join, relative, relativeImport } from '@utils';
32

43
import type * as d from '../../declarations';
54
import { getClientPolyfill } from '../app-core/app-polyfills';
@@ -49,18 +48,18 @@ const generateLoader = async (
4948
const es2017EntryPoint = join(es2017Dir, 'loader.js');
5049
const polyfillsEntryPoint = join(es2017Dir, 'polyfills/index.js');
5150
const cjsEntryPoint = join(cjsDir, 'loader.cjs.js');
52-
const polyfillsExport = `export * from '${normalizePath(relative(loaderPath, polyfillsEntryPoint))}';`;
51+
const polyfillsExport = `export * from '${relative(loaderPath, polyfillsEntryPoint)}';`;
5352
const indexContent = `${generatePreamble(config)}
5453
${es5HtmlElement}
5554
${polyfillsExport}
56-
export * from '${normalizePath(relative(loaderPath, es5EntryPoint))}';
55+
export * from '${relative(loaderPath, es5EntryPoint)}';
5756
`;
5857
const indexES2017Content = `${generatePreamble(config)}
5958
${polyfillsExport}
60-
export * from '${normalizePath(relative(loaderPath, es2017EntryPoint))}';
59+
export * from '${relative(loaderPath, es2017EntryPoint)}';
6160
`;
6261
const indexCjsContent = `${generatePreamble(config)}
63-
module.exports = require('${normalizePath(relative(loaderPath, cjsEntryPoint))}');
62+
module.exports = require('${relative(loaderPath, cjsEntryPoint)}');
6463
module.exports.applyPolyfills = function() { return Promise.resolve() };
6564
`;
6665

‎src/compiler/output-targets/output-www.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { cloneDocument, serializeNodeToHtml } from '@stencil/core/mock-doc';
2-
import { catchError, flatOne, isOutputTargetWww, unique } from '@utils';
3-
import { join, relative } from 'path';
2+
import { catchError, flatOne, isOutputTargetWww, join, relative, unique } from '@utils';
43

54
import type * as d from '../../declarations';
65
import { generateEs5DisabledMessage } from '../app-core/app-es5-disabled';

‎src/compiler/output-targets/test/custom-elements-types.spec.ts

+8-15
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import {
55
mockModule,
66
mockValidatedConfig,
77
} from '@stencil/core/testing';
8-
import { DIST_CUSTOM_ELEMENTS } from '@utils';
8+
import { DIST_CUSTOM_ELEMENTS, normalizePath } from '@utils';
99
import path from 'path';
10-
import { join, relative } from 'path';
1110

1211
import type * as d from '../../../declarations';
1312
import { stubComponentCompilerMeta } from '../../types/tests/ComponentCompilerMeta.stub';
@@ -29,8 +28,8 @@ const setup = () => {
2928
const buildCtx = mockBuildCtx(config, compilerCtx);
3029

3130
const root = config.rootDir;
32-
config.rootDir = path.join(root, 'User', 'testing', '/');
33-
config.globalScript = path.join(root, 'User', 'testing', 'src', 'global.ts');
31+
config.rootDir = normalizePath(path.join(root, 'User', 'testing', '/'));
32+
config.globalScript = normalizePath(path.join(root, 'User', 'testing', 'src', 'global.ts'));
3433

3534
const bundleCustomElementsSpy = jest.spyOn(outputCustomElementsMod, 'bundleCustomElements');
3635

@@ -62,17 +61,11 @@ describe('Custom Elements Typedef generation', () => {
6261

6362
await generateCustomElementsTypes(config, compilerCtx, buildCtx, 'types_dir');
6463

65-
const componentsTypeDirectoryPath = relative('my-best-dir', join('types_dir', 'components'));
66-
6764
const expectedTypedefOutput = [
6865
'/* TestApp custom elements */',
69-
`export { StubCmp as MyComponent } from '${join(componentsTypeDirectoryPath, 'my-component', 'my-component')}';`,
66+
`export { StubCmp as MyComponent } from '../types_dir/components/my-component/my-component';`,
7067
`export { defineCustomElement as defineCustomElementMyComponent } from './my-component';`,
71-
`export { MyBestComponent as MyBestComponent } from '${join(
72-
componentsTypeDirectoryPath,
73-
'the-other-component',
74-
'my-real-best-component'
75-
)}';`,
68+
`export { MyBestComponent as MyBestComponent } from '../types_dir/components/the-other-component/my-real-best-component';`,
7669
`export { defineCustomElement as defineCustomElementMyBestComponent } from './my-best-component';`,
7770
'',
7871
'/**',
@@ -106,7 +99,7 @@ describe('Custom Elements Typedef generation', () => {
10699
'',
107100
].join('\n');
108101

109-
expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith(join('my-best-dir', 'index.d.ts'), expectedTypedefOutput, {
102+
expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith('my-best-dir/index.d.ts', expectedTypedefOutput, {
110103
outputTargetType: DIST_CUSTOM_ELEMENTS,
111104
});
112105

@@ -165,7 +158,7 @@ describe('Custom Elements Typedef generation', () => {
165158
'',
166159
].join('\n');
167160

168-
expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith(join('my-best-dir', 'index.d.ts'), expectedTypedefOutput, {
161+
expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith('my-best-dir/index.d.ts', expectedTypedefOutput, {
169162
outputTargetType: DIST_CUSTOM_ELEMENTS,
170163
});
171164

@@ -233,7 +226,7 @@ describe('Custom Elements Typedef generation', () => {
233226
'',
234227
].join('\n');
235228

236-
expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith(join('my-best-dir', 'index.d.ts'), expectedTypedefOutput, {
229+
expect(compilerCtx.fs.writeFile).toHaveBeenCalledWith('my-best-dir/index.d.ts', expectedTypedefOutput, {
237230
outputTargetType: DIST_CUSTOM_ELEMENTS,
238231
});
239232

‎src/compiler/output-targets/test/output-targets-collection.spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { mockBuildCtx, mockCompilerCtx, mockModule, mockValidatedConfig } from '@stencil/core/testing';
2-
import { normalize } from 'path';
32

43
import type * as d from '../../../declarations';
54
import * as test from '../../transformers/map-imports-to-path-aliases';
@@ -61,7 +60,7 @@ describe('Dist Collection output target', () => {
6160

6261
await outputCollection(mockConfig, mockedCompilerCtx, mockedBuildCtx, changedModules);
6362

64-
expect(mapImportPathSpy).toHaveBeenCalledWith(mockConfig, normalize('/dist/collection/main.js'), {
63+
expect(mapImportPathSpy).toHaveBeenCalledWith(mockConfig, '/dist/collection/main.js', {
6564
collectionDir: '/dist/collection',
6665
dir: '',
6766
transformAliasedImportPaths,

‎src/compiler/prerender/prerender-queue.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { buildError, catchError, isFunction, isString } from '@utils';
2-
import { relative } from 'path';
1+
import { buildError, catchError, isFunction, isString, relative } from '@utils';
32

43
import type * as d from '../../declarations';
54
import { crawlAnchorsForNextUrls } from './crawl-urls';

‎src/compiler/service-worker/service-worker-util.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { normalizePath } from '@utils';
2-
import { relative } from 'path';
1+
import { relative } from '@utils';
32

43
import type * as d from '../../declarations';
54

65
export const generateServiceWorkerUrl = (outputTarget: d.OutputTargetWww, serviceWorker: d.ServiceWorkerConfig) => {
7-
let swUrl = normalizePath(relative(outputTarget.appDir, serviceWorker.swDest));
6+
let swUrl = relative(outputTarget.appDir, serviceWorker.swDest);
87

98
if (swUrl.charAt(0) !== '/') {
109
swUrl = '/' + swUrl;

‎src/compiler/transformers/collections/parse-collection-module.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { normalizePath } from '@utils';
2-
import { dirname, join, relative } from 'path';
1+
import { join, normalizePath, relative } from '@utils';
2+
import { dirname } from 'path';
33

44
import type * as d from '../../../declarations';
55
import { parseCollectionManifest } from './parse-collection-manifest';

‎src/compiler/transformers/map-imports-to-path-aliases.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { normalizePath } from '@utils';
2-
import { dirname, relative } from 'path';
1+
import { normalizePath, relative } from '@utils';
2+
import { dirname } from 'path';
33
import ts from 'typescript';
44

55
import type * as d from '../../declarations';

‎src/compiler/transformers/rewrite-aliased-paths.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { normalizePath } from '@utils';
2-
import { dirname, relative } from 'path';
1+
import { normalizePath, relative } from '@utils';
2+
import { dirname } from 'path';
33
import ts from 'typescript';
44

55
import { retrieveTsModifiers } from './transform-utils';

‎src/compiler/transformers/static-to-meta/component.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { normalizePath, unique } from '@utils';
2-
import { dirname, isAbsolute, join, relative } from 'path';
1+
import { join, normalizePath, relative, unique } from '@utils';
2+
import { dirname, isAbsolute } from 'path';
33
import ts from 'typescript';
44

55
import type * as d from '../../../declarations';

‎src/compiler/transformers/stencil-import-path.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { DEFAULT_STYLE_MODE, isString, normalizePath } from '@utils';
2-
import { basename, dirname, isAbsolute, relative } from 'path';
1+
import { DEFAULT_STYLE_MODE, isString, relative } from '@utils';
2+
import { basename, dirname, isAbsolute } from 'path';
33

44
import type { ImportData, ParsedImport, SerializeImportData } from '../../declarations';
55

@@ -24,7 +24,6 @@ export const serializeImportPath = (data: SerializeImportData, styleImportData:
2424
if (isString(data.importerPath) && isAbsolute(data.importeePath)) {
2525
p = relative(dirname(data.importerPath), data.importeePath);
2626
}
27-
p = normalizePath(p);
2827
if (!p.startsWith('.')) {
2928
p = './' + p;
3029
}

‎src/compiler/transformers/type-library.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { normalizePath } from '@utils';
2-
import path from 'path';
1+
import { normalizePath, relative } from '@utils';
32
import ts from 'typescript';
43

54
import type * as d from '../../declarations';
@@ -30,8 +29,7 @@ export function addToLibrary(
3029
checker: ts.TypeChecker,
3130
pathToTypeModule: string
3231
): string {
33-
pathToTypeModule = path.relative(process.cwd(), pathToTypeModule);
34-
pathToTypeModule = normalizePath(pathToTypeModule, false);
32+
pathToTypeModule = relative(process.cwd(), pathToTypeModule);
3533

3634
// for now we don't make any attempt to include types in node_modules
3735
if (pathToTypeModule.startsWith('node_modules')) {

‎src/compiler/transpile/run-program.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
import { getComponentsFromModules, isOutputTargetDistTypes, loadTypeScriptDiagnostics, normalizePath } from '@utils';
2-
import { basename, join, relative } from 'path';
1+
import {
2+
getComponentsFromModules,
3+
isOutputTargetDistTypes,
4+
join,
5+
loadTypeScriptDiagnostics,
6+
normalizePath,
7+
relative,
8+
} from '@utils';
9+
import { basename } from 'path';
310
import ts from 'typescript';
411

512
import type * as d from '../../declarations';

‎src/compiler/transpile/validate-components.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { buildError } from '@utils';
2-
import { relative } from 'path';
1+
import { buildError, relative } from '@utils';
32

43
import type * as d from '../../declarations';
54

‎src/utils/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ export * from './logger/logger-rollup';
88
export * from './logger/logger-typescript';
99
export * from './logger/logger-utils';
1010
export * from './message-utils';
11-
export * from './normalize-path';
1211
export * from './output-target';
12+
export * from './path';
1313
export * from './query-nonce-meta-tag-content';
1414
export * from './sourcemaps';
1515
export * from './url-paths';

‎src/utils/logger/logger-typescript.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Diagnostic, DiagnosticMessageChain, Node } from 'typescript';
22

33
import type * as d from '../../declarations';
44
import { isIterable } from '../helpers';
5-
import { normalizePath } from '../normalize-path';
5+
import { normalizePath } from '../path';
66
import { splitLineBreaks } from './logger-utils';
77

88
/**

‎src/utils/normalize-path.ts ‎src/utils/path.ts

+30
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import path from 'path';
2+
13
/**
24
* Convert Windows backslash paths to slash paths: foo\\bar ➔ foo/bar
35
* Forward-slash paths can be used in Windows as long as they're not
@@ -187,3 +189,31 @@ const enum CharacterCodes {
187189
percent = 0x25, // %
188190
slash = 0x2f, // /
189191
}
192+
193+
/**
194+
* A wrapped version of node.js' {@link path.relative} which adds our custom
195+
* normalization logic. This solves the relative path between `from` and `to`!
196+
*
197+
* @throws the underlying node.js function can throw if either path is not a
198+
* string
199+
* @param from the path where relative resolution starts
200+
* @param to the destination path
201+
* @returns the resolved relative path
202+
*/
203+
export function relative(from: string, to: string): string {
204+
return normalizePath(path.relative(from, to), false);
205+
}
206+
207+
/**
208+
* A wrapped version of node.js' {@link path.join} which adds our custom
209+
* normalization logic. This joins all the arguments (path fragments) into a
210+
* single path.
211+
*
212+
* @throws the underlying node function will throw if any argument is not a
213+
* string
214+
* @param paths the paths to join together
215+
* @returns a joined path!
216+
*/
217+
export function join(...paths: string[]): string {
218+
return normalizePath(path.join(...paths), false);
219+
}

‎src/utils/test/normalize-path.spec.ts ‎src/utils/test/path.spec.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { normalizeFsPathQuery, normalizePath } from '../normalize-path';
1+
import { join, normalizeFsPathQuery, normalizePath, relative } from '../path';
22

33
describe('normalizePath', () => {
44
it('node module', () => {
@@ -133,4 +133,20 @@ describe('normalizeFsPathQuery', () => {
133133
expect(p.format).toBe(null);
134134
expect(p.ext).toBe(`/johnny/b/goode`);
135135
});
136+
137+
describe('wrapped nodejs path functions', () => {
138+
it('join should always return a POSIX path', () => {
139+
expect(join('foo')).toBe('foo');
140+
expect(join('foo', 'bar')).toBe('foo/bar');
141+
expect(join('..', 'foo', 'bar.ts')).toBe('../foo/bar.ts');
142+
expect(join('.', 'foo', 'bar.ts')).toBe('foo/bar.ts');
143+
});
144+
145+
it('relative should always return a POSIX path', () => {
146+
expect(relative('.', 'foo/bar')).toBe('foo/bar');
147+
expect(relative('foo/bar', '..')).toBe('../../..');
148+
expect(relative('foo/bar/baz', 'foo/bar/boz')).toBe('../boz');
149+
expect(relative('.', '../foo/bar')).toBe('../foo/bar');
150+
});
151+
});
136152
});

0 commit comments

Comments
 (0)
Please sign in to comment.