diff --git a/packages/compat/package.json b/packages/compat/package.json index 971ea0da8..33e141aa7 100644 --- a/packages/compat/package.json +++ b/packages/compat/package.json @@ -57,6 +57,7 @@ "lodash": "^4.17.21", "pkg-up": "^3.1.0", "resolve": "^1.20.0", + "resolve.exports": "^2.0.2", "resolve-package-path": "^4.0.1", "semver": "^7.3.5", "symlink-or-copy": "^1.3.1", diff --git a/packages/compat/src/compat-app-builder.ts b/packages/compat/src/compat-app-builder.ts index 97b2fc975..a6f50740c 100644 --- a/packages/compat/src/compat-app-builder.ts +++ b/packages/compat/src/compat-app-builder.ts @@ -509,6 +509,13 @@ export class CompatAppBuilder { if ((this.origAppPackage.packageJSON['ember-addon']?.version ?? 0) < 2) { meta['auto-upgraded'] = true; + // our rewriting keeps app in app directory, etc. + pkgLayers.push({ + exports: { + './*': './app/*', + './tests/*': './tests/*', + }, + }); } pkgLayers.push({ 'ember-addon': meta }); diff --git a/packages/compat/src/compat-app.ts b/packages/compat/src/compat-app.ts index 963df0d97..d231ceae5 100644 --- a/packages/compat/src/compat-app.ts +++ b/packages/compat/src/compat-app.ts @@ -618,6 +618,7 @@ export default class CompatApp { return this.preprocessJS( buildFunnel(this.legacyEmberAppInstance.trees.app, { exclude: ['styles/**', '*.html'], + destDir: 'app', }) ); } diff --git a/packages/compat/src/dependency-rules.ts b/packages/compat/src/dependency-rules.ts index 502a76d58..15f8d326a 100644 --- a/packages/compat/src/dependency-rules.ts +++ b/packages/compat/src/dependency-rules.ts @@ -1,7 +1,8 @@ import type { Resolver } from '@embroider/core'; import { getOrCreate } from '@embroider/core'; -import { resolve } from 'path'; +import { resolve as pathResolve, dirname } from 'path'; import { satisfies } from 'semver'; +import { resolve as resolveExports } from 'resolve.exports'; export interface PackageRules { // This whole set of rules will only apply when the given addon package @@ -232,14 +233,20 @@ export function activePackageRules( export function appTreeRulesDir(root: string, resolver: Resolver) { let pkg = resolver.packageCache.ownerOfFile(root); - if (pkg?.isV2Addon()) { - // in general v2 addons can keep their app tree stuff in other places than - // "_app_" and we would need to check their package.json to see. But this code - // is only for applying packageRules to auto-upgraded v1 addons and apps, and - // those we always organize predictably. - return resolve(root, '_app_'); - } else { - // auto-upgraded apps don't get an exist _app_ dir. - return root; + if (pkg) { + if (pkg.isV2Addon()) { + // in general v2 addons can keep their app tree stuff in other places than + // "_app_" and we would need to check their package.json to see. But this code + // is only for applying packageRules to auto-upgraded v1 addons and apps, and + // those we always organize predictably. + return pathResolve(root, '_app_'); + } else { + // this is an app + let matched = resolveExports(pkg.packageJSON, './index.js'); + if (matched) { + return dirname(pathResolve(root, matched[0])); + } + } } + return root; } diff --git a/packages/compat/src/resolver-transform.ts b/packages/compat/src/resolver-transform.ts index 395c804a3..967f5bcaa 100644 --- a/packages/compat/src/resolver-transform.ts +++ b/packages/compat/src/resolver-transform.ts @@ -547,17 +547,17 @@ class TemplateResolver implements ASTPlugin { 2. Have a mustache statement like: `{{something}}`, where `something` is: - a. Not a variable in scope (for example, there's no preceeding line + a. Not a variable in scope (for example, there's no preceeding line like ``) b. Does not start with `@` because that must be an argument from outside this template. - c. Does not contain a dot, like `some.thing` (because that case is classically + c. Does not contain a dot, like `some.thing` (because that case is classically never a global component resolution that we would need to handle) - d. Does not start with `this` (this rule is mostly redundant with the previous rule, + d. Does not start with `this` (this rule is mostly redundant with the previous rule, but even a standalone `this` is never a component invocation). - e. Does not have any arguments. If there are argument like `{{something a=b}}`, - there is still ambiguity between helper vs component, but there is no longer + e. Does not have any arguments. If there are argument like `{{something a=b}}`, + there is still ambiguity between helper vs component, but there is no longer the possibility that this was just rendering some data. - f. Does not take a block, like `{{#something}}{{/something}}` (because that is + f. Does not take a block, like `{{#something}}{{/something}}` (because that is always a component, no ambiguity.) We can't tell if this problematic case is really: @@ -571,7 +571,7 @@ class TemplateResolver implements ASTPlugin { 2. A component invocation, which you could have written `` instead. Angle-bracket invocation has been available and easy-to-adopt - for a very long time. + for a very long time. 3. Property-this-fallback for `{{this.something}}`. Property-this-fallback is eliminated at Ember 4.0, so people have been heavily pushed to get diff --git a/packages/compat/tests/audit.test.ts b/packages/compat/tests/audit.test.ts index d6b40712e..4634d5cad 100644 --- a/packages/compat/tests/audit.test.ts +++ b/packages/compat/tests/audit.test.ts @@ -104,6 +104,10 @@ describe('audit', function () { merge(app.pkg, { 'ember-addon': appMeta, keywords: ['ember-addon'], + exports: { + './*': './*', + './tests/*': './tests/*', + }, }); }); diff --git a/packages/core/src/module-resolver.ts b/packages/core/src/module-resolver.ts index 150f0368f..bad3a9994 100644 --- a/packages/core/src/module-resolver.ts +++ b/packages/core/src/module-resolver.ts @@ -465,8 +465,15 @@ export class Resolver { } else { pkg = requestingPkg; } - - return logTransition('entrypoint', request, request.virtualize(resolve(pkg.root, '-embroider-entrypoint.js'))); + let matched = resolveExports(pkg.packageJSON, '-embroider-entrypoint.js', { + browser: true, + conditions: ['default', 'imports'], + }); + return logTransition( + 'entrypoint', + request, + request.virtualize(resolve(pkg.root, matched?.[0] ?? '-embroider-entrypoint.js')) + ); } private handleRouteEntrypoint(request: R): R { @@ -486,7 +493,16 @@ export class Resolver { throw new Error(`bug: found entrypoint import in non-ember package at ${request.fromFile}`); } - return logTransition('route entrypoint', request, request.virtualize(encodeRouteEntrypoint(pkg.root, routeName))); + let matched = resolveExports(pkg.packageJSON, '-embroider-route-entrypoint.js', { + browser: true, + conditions: ['default', 'imports'], + }); + + return logTransition( + 'route entrypoint', + request, + request.virtualize(encodeRouteEntrypoint(pkg.root, matched?.[0], routeName)) + ); } private handleImplicitTestScripts(request: R): R { @@ -963,6 +979,11 @@ export class Resolver { // ember-source might provide backburner via module renaming, but if you // have an explicit dependency on backburner you should still get that real // copy. + + // if (pkg.root === this.options.engines[0].root && request.specifier === `${pkg.name}/environment/config`) { + // return logTransition('legacy config location', request, request.alias(`${pkg.name}/app/environment/config`)); + // } + if (!pkg.hasDependency(packageName)) { for (let [candidate, replacement] of Object.entries(this.options.renameModules)) { if (candidate === request.specifier) { @@ -1000,35 +1021,33 @@ export class Resolver { let owningEngine = this.owningEngine(pkg); let addonConfig = owningEngine.activeAddons.find(a => a.root === pkg.root); if (addonConfig) { + // auto-upgraded addons get special support for self-resolving here. return logTransition(`v1 addon self-import`, request, request.rehome(addonConfig.canResolveFromFile)); } else { - let selfImportPath = request.specifier === pkg.name ? './' : request.specifier.replace(pkg.name, '.'); + // auto-upgraded apps will necessarily have packageJSON.exports + // because we insert them, so for that support we can fall through to + // that support below. + } + } + + // v2 packages are supposed to use package.json `exports` to enable + // self-imports, but not all build tools actually follow the spec. This + // is a workaround for badly behaved packagers. + // + // Known upstream bugs this works around: + // - https://github.com/vitejs/vite/issues/9731 + if (pkg.packageJSON.exports) { + let found = resolveExports(pkg.packageJSON, request.specifier, { + browser: true, + conditions: ['default', 'imports'], + }); + if (found?.[0]) { return logTransition( - `v1 app self-import`, + `v2 self-import with package.json exports`, request, - request.alias(selfImportPath).rehome(resolve(pkg.root, 'package.json')) + request.alias(found?.[0]).rehome(resolve(pkg.root, 'package.json')) ); } - } else { - // v2 packages are supposed to use package.json `exports` to enable - // self-imports, but not all build tools actually follow the spec. This - // is a workaround for badly behaved packagers. - // - // Known upstream bugs this works around: - // - https://github.com/vitejs/vite/issues/9731 - if (pkg.packageJSON.exports) { - let found = resolveExports(pkg.packageJSON, request.specifier, { - browser: true, - conditions: ['default', 'imports'], - }); - if (found?.[0]) { - return logTransition( - `v2 self-import with package.json exports`, - request, - request.alias(found?.[0]).rehome(resolve(pkg.root, 'package.json')) - ); - } - } } } @@ -1105,21 +1124,15 @@ export class Resolver { // if the requesting file is in an addon's app-js, the relative request // should really be understood as a request for a module in the containing - // engine + // engine. let logicalLocation = this.reverseSearchAppTree(pkg, request.fromFile); if (logicalLocation) { return logTransition( 'beforeResolve: relative import in app-js', request, - request - .alias('./' + posix.join(dirname(logicalLocation.inAppName), request.specifier)) - // it's important that we're rehoming this to the root of the engine - // (which we know really exists), and not to a subdir like - // logicalLocation.inAppName (which might not physically exist), - // because some environments (including node's require.resolve) will - // refuse to do resolution from a notional path that doesn't - // physically exist. - .rehome(resolve(logicalLocation.owningEngine.root, 'package.json')) + request.alias( + posix.join(logicalLocation.owningEngine.packageName, dirname(logicalLocation.inAppName), request.specifier) + ) ); } @@ -1301,11 +1314,15 @@ export class Resolver { if (withinEngine) { // it's a relative import inside an engine (which also means app), which // means we may need to satisfy the request via app tree merging. - let appJSMatch = await this.searchAppTree( - request, - withinEngine, - explicitRelative(pkg.root, resolve(dirname(request.fromFile), request.specifier)) - ); + + let logicalName = engineRelativeName(pkg, resolve(dirname(request.fromFile), request.specifier)); + if (!logicalName) { + return logTransition( + 'fallbackResolve: relative failure because this file is not externally accessible', + request + ); + } + let appJSMatch = await this.searchAppTree(request, withinEngine, logicalName); if (appJSMatch) { return logTransition('fallbackResolve: relative appJsMatch', request, appJSMatch); } else { @@ -1462,16 +1479,10 @@ export class Resolver { if (engineConfig) { // we're directly inside an engine, so we're potentially resolvable as a // global component - - // this kind of mapping is not true in general for all packages, but it - // *is* true for all classical engines (which includes apps) since they - // don't support package.json `exports`. As for a future v2 engine or app: - // this whole method is only relevant for implementing packageRules, which - // should only be for classic stuff. v2 packages should do the right - // things from the beginning and not need packageRules about themselves. - let inAppName = explicitRelative(engineConfig.root, filename); - - return this.tryReverseComponent(engineConfig.packageName, inAppName); + let inAppName = engineRelativeName(owningPackage, filename); + if (inAppName) { + return this.tryReverseComponent(engineConfig.packageName, inAppName); + } } let engineInfo = this.reverseSearchAppTree(owningPackage, filename); @@ -1523,3 +1534,10 @@ function reliablyResolvable(pkg: V2Package, packageName: string) { function appImportInAppTree(inPackage: Package, inLogicalPackage: Package, importedPackageName: string): boolean { return inPackage !== inLogicalPackage && importedPackageName === inLogicalPackage.name; } + +function engineRelativeName(pkg: Package, filename: string): string | undefined { + let outsideName = externalName(pkg.packageJSON, explicitRelative(pkg.root, filename)); + if (outsideName) { + return '.' + outsideName.slice(pkg.name.length); + } +} diff --git a/packages/core/src/virtual-entrypoint.ts b/packages/core/src/virtual-entrypoint.ts index 257e6b7a1..a74c5648a 100644 --- a/packages/core/src/virtual-entrypoint.ts +++ b/packages/core/src/virtual-entrypoint.ts @@ -12,7 +12,7 @@ import escapeRegExp from 'escape-string-regexp'; const entrypointPattern = /(?.*)[\\/]-embroider-entrypoint.js/; -export function decodeEntrypoint(filename: string): { fromFile: string } | undefined { +export function decodeEntrypoint(filename: string): { fromDir: string } | undefined { // Performance: avoid paying regex exec cost unless needed if (!filename.includes('-embroider-entrypoint')) { return; @@ -20,7 +20,7 @@ export function decodeEntrypoint(filename: string): { fromFile: string } | undef let m = entrypointPattern.exec(filename); if (m) { return { - fromFile: m.groups!.filename, + fromDir: m.groups!.filename, }; } } @@ -33,10 +33,10 @@ export function staticAppPathsPattern(staticAppPaths: string[] | undefined): Reg export function renderEntrypoint( resolver: Resolver, - { fromFile }: { fromFile: string } + { fromDir }: { fromDir: string } ): { src: string; watches: string[] } { // this is new - const owner = resolver.packageCache.ownerOfFile(fromFile); + const owner = resolver.packageCache.ownerOfFile(fromDir); let eagerModules: string[] = []; @@ -61,7 +61,7 @@ export function renderEntrypoint( modulePrefix: isApp ? resolver.options.modulePrefix : engine.packageName, appRelativePath: 'NOT_USED_DELETE_ME', }, - getAppFiles(owner.root), + getAppFiles(fromDir), hasFastboot ? getFastbootFiles(owner.root) : new Set(), extensionsPattern(resolver.options.resolvableExtensions), staticAppPathsPattern(resolver.options.staticAppPaths), @@ -154,8 +154,6 @@ export function renderEntrypoint( const entryTemplate = compile(` import { macroCondition, getGlobalConfig } from '@embroider/macros'; -import environment from './config/environment'; - {{#if styles}} if (macroCondition(!getGlobalConfig().fastboot?.isRunning)) { {{#each styles as |stylePath| ~}} diff --git a/packages/core/src/virtual-route-entrypoint.ts b/packages/core/src/virtual-route-entrypoint.ts index 82f589fe6..32a7af5a6 100644 --- a/packages/core/src/virtual-route-entrypoint.ts +++ b/packages/core/src/virtual-route-entrypoint.ts @@ -9,11 +9,11 @@ import { getAppFiles, getFastbootFiles, importPaths, splitRoute, staticAppPathsP const entrypointPattern = /(?.*)[\\/]-embroider-route-entrypoint.js:route=(?.*)/; -export function encodeRouteEntrypoint(packagePath: string, routeName: string): string { - return resolve(packagePath, `-embroider-route-entrypoint.js:route=${routeName}`); +export function encodeRouteEntrypoint(packagePath: string, matched: string | undefined, routeName: string): string { + return resolve(packagePath, `${matched}:route=${routeName}` ?? `-embroider-route-entrypoint.js:route=${routeName}`); } -export function decodeRouteEntrypoint(filename: string): { fromFile: string; route: string } | undefined { +export function decodeRouteEntrypoint(filename: string): { fromDir: string; route: string } | undefined { // Performance: avoid paying regex exec cost unless needed if (!filename.includes('-embroider-route-entrypoint')) { return; @@ -21,7 +21,7 @@ export function decodeRouteEntrypoint(filename: string): { fromFile: string; rou let m = entrypointPattern.exec(filename); if (m) { return { - fromFile: m.groups!.filename, + fromDir: m.groups!.filename, route: m.groups!.route, }; } @@ -42,9 +42,9 @@ export function decodePublicRouteEntrypoint(specifier: string): string | null { export function renderRouteEntrypoint( resolver: Resolver, - { fromFile, route }: { fromFile: string; route: string } + { fromDir, route }: { fromDir: string; route: string } ): { src: string; watches: string[] } { - const owner = resolver.packageCache.ownerOfFile(fromFile); + const owner = resolver.packageCache.ownerOfFile(fromDir); if (!owner) { throw new Error('Owner expected'); // ToDo: Really bad error, update message @@ -67,7 +67,7 @@ export function renderRouteEntrypoint( modulePrefix: isApp ? resolver.options.modulePrefix : engine.packageName, appRelativePath: 'NOT_USED_DELETE_ME', }, - getAppFiles(owner.root), + getAppFiles(fromDir), hasFastboot ? getFastbootFiles(owner.root) : new Set(), extensionsPattern(resolver.options.resolvableExtensions), staticAppPathsPattern(resolver.options.staticAppPaths), diff --git a/packages/shared-internals/package.json b/packages/shared-internals/package.json index c6aa44a28..4da247271 100644 --- a/packages/shared-internals/package.json +++ b/packages/shared-internals/package.json @@ -37,6 +37,7 @@ "fs-extra": "^9.1.0", "lodash": "^4.17.21", "minimatch": "^3.0.4", + "resolve.exports": "^2.0.2", "semver": "^7.3.5" }, "devDependencies": { diff --git a/packages/shared-internals/src/colocation.ts b/packages/shared-internals/src/colocation.ts index e22101555..8a732ad91 100644 --- a/packages/shared-internals/src/colocation.ts +++ b/packages/shared-internals/src/colocation.ts @@ -2,6 +2,7 @@ import { existsSync } from 'fs-extra'; import { cleanUrl } from './paths'; import type PackageCache from './package-cache'; import { sep } from 'path'; +import { resolve as resolveExports } from 'resolve.exports'; export function syntheticJStoHBS(source: string): string | null { // explicit js is the only case we care about here. Synthetic template JS is @@ -40,9 +41,18 @@ function correspondingJSExists(id: string): boolean { return ['js', 'ts'].some(ext => existsSync(id.slice(0, -3) + ext)); } -function isInComponents(id: string, packageCache: Pick) { +export function isInComponents(id: string, packageCache: Pick) { const pkg = packageCache.ownerOfFile(id); - return pkg?.isV2App() && id.slice(pkg?.root.length).split(sep).join('/').startsWith('/components'); + if (!pkg?.isV2App()) { + return false; + } + + let tryResolve = resolveExports(pkg.packageJSON, './components', { + browser: true, + conditions: ['default', 'imports'], + }); + let componentsDir = tryResolve?.[0] ?? './components'; + return ('.' + id.slice(pkg?.root.length).split(sep).join('/')).startsWith(componentsDir); } export function templateOnlyComponentSource() { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14e9e146d..306a6e478 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -277,6 +277,9 @@ importers: resolve-package-path: specifier: ^4.0.1 version: 4.0.3 + resolve.exports: + specifier: ^2.0.2 + version: 2.0.2 semver: specifier: ^7.3.5 version: 7.6.3 @@ -710,6 +713,9 @@ importers: resolve-package-path: specifier: ^4.0.1 version: 4.0.3 + resolve.exports: + specifier: ^2.0.2 + version: 2.0.2 semver: specifier: ^7.3.5 version: 7.6.3 diff --git a/tests/addon-template/tests/dummy/app/index.html b/tests/addon-template/tests/dummy/app/index.html index c55e417ab..44af3dc9b 100644 --- a/tests/addon-template/tests/dummy/app/index.html +++ b/tests/addon-template/tests/dummy/app/index.html @@ -18,8 +18,8 @@ diff --git a/tests/app-template/app/index.html b/tests/app-template/app/index.html index c53dfc7da..60a4b93e5 100644 --- a/tests/app-template/app/index.html +++ b/tests/app-template/app/index.html @@ -18,8 +18,8 @@ diff --git a/tests/app-template/package.json b/tests/app-template/package.json index 978fc99dc..0d674cf66 100644 --- a/tests/app-template/package.json +++ b/tests/app-template/package.json @@ -11,7 +11,8 @@ "test": "tests" }, "exports": { - "./*": "./*" + "./tests/*": "./tests/*", + "./*": "./app/*" }, "scripts": { "build": "vite build", diff --git a/tests/fixtures/macro-sample-addon/tests/dummy/app/index.html b/tests/fixtures/macro-sample-addon/tests/dummy/app/index.html index 24d04a975..8dfc7c9bd 100644 --- a/tests/fixtures/macro-sample-addon/tests/dummy/app/index.html +++ b/tests/fixtures/macro-sample-addon/tests/dummy/app/index.html @@ -18,8 +18,8 @@ - + {{content-for "body-footer"}} diff --git a/tests/scenarios/compat-dummy-app-test.ts b/tests/scenarios/compat-dummy-app-test.ts index 1ffd69f6e..29c798a5a 100644 --- a/tests/scenarios/compat-dummy-app-test.ts +++ b/tests/scenarios/compat-dummy-app-test.ts @@ -76,7 +76,8 @@ dummyAppScenarios }); test('production build contains public assets from both addon and dummy app after a build', async function (assert) { - await app.execute(`pnpm vite build`); + let result = await app.execute(`pnpm vite build`); + assert.equal(result.exitCode, 0, result.output); let content = readFileSync(`${app.dir}/dist/robots.txt`).toString(); assert.strictEqual(content, 'go away bots'); content = readFileSync(`${app.dir}/dist/addon-template/from-addon.txt`).toString(); diff --git a/tests/scenarios/compat-exclude-dot-files-test.ts b/tests/scenarios/compat-exclude-dot-files-test.ts index 0e6756640..270e4c508 100644 --- a/tests/scenarios/compat-exclude-dot-files-test.ts +++ b/tests/scenarios/compat-exclude-dot-files-test.ts @@ -79,9 +79,9 @@ appScenarios test('dot files are not included as app modules', function (assert) { // dot files should exist on disk - expectFile('./.foobar.js').exists(); - expectFile('./.barbaz.js').exists(); - expectFile('./bizbiz.js').exists(); + expectFile('./app/.foobar.js').exists(); + expectFile('./app/.barbaz.js').exists(); + expectFile('./app/bizbiz.js').exists(); // but not be picked up in the entrypoint expectAudit diff --git a/tests/scenarios/compat-preprocessors-test.ts b/tests/scenarios/compat-preprocessors-test.ts index f8bd78979..0a607db83 100644 --- a/tests/scenarios/compat-preprocessors-test.ts +++ b/tests/scenarios/compat-preprocessors-test.ts @@ -114,10 +114,13 @@ appScenarios }); test('app has correct path embedded in comment', () => { - const assertFile = expectFile('./components/from-the-app.js'); + const assertFile = expectFile('./app/components/from-the-app.js'); assertFile.exists(); // This is the expected output during an classic build. - assertFile.matches(/path@app-template\/components\/from-the-app\.js/, 'has a path comment in app components'); + assertFile.matches( + /path@app-template\/app\/components\/from-the-app\.js/, + 'has a path comment in app components' + ); }); test('addon has correct path embedded in comment', () => { diff --git a/tests/scenarios/compat-route-split-test.ts b/tests/scenarios/compat-route-split-test.ts index 116b1f2b5..4b05ebccb 100644 --- a/tests/scenarios/compat-route-split-test.ts +++ b/tests/scenarios/compat-route-split-test.ts @@ -68,7 +68,7 @@ function checkContents( .module('./index.html') .resolves(/\/index.html.*/) // in-html app-boot script .toModule() - .resolves(/\/app\.js.*/) + .resolves(/\/app\/app\.js.*/) .toModule() .resolves(/.*\/-embroider-entrypoint.js/); @@ -112,7 +112,9 @@ function inEntrypointFunction(expectAudit: ReturnType) { if (Array.isArray(text)) { text.forEach(t => { if (!contents.includes(t)) { - throw new Error(`${t} should be found in entrypoint`); + throw new Error(`${t} should be found in entrypoint: +--- +${contents}`); } }); } else if (text instanceof RegExp) { @@ -229,21 +231,21 @@ splitScenarios test('has split controllers in route entrypoint', function () { inEntrypoint( - ['controllers/people', 'controllers/people/show'], + ['app/controllers/people', 'app/controllers/people/show'], /@id\/embroider_virtual:.*-embroider-route-entrypoint.js:route=people/ ); }); test('has split route templates in route entrypoint', function () { inEntrypoint( - ['templates/people', 'templates/people/index', 'templates/people/show'], + ['app/templates/people', 'app/templates/people/index', 'app/templates/people/show'], /@id\/embroider_virtual:.*-embroider-route-entrypoint.js:route=people/ ); }); test('has split routes in route entrypoint', function () { inEntrypoint( - ['routes/people', 'routes/people/show'], + ['app/routes/people', 'app/routes/people/show'], /@id\/embroider_virtual:.*-embroider-route-entrypoint.js:route=people/ ); }); @@ -268,19 +270,19 @@ splitScenarios }); test('helper is consumed only from the template that uses it', function () { - expectAudit.module('./helpers/capitalize.js').hasConsumers(['./components/one-person.hbs']); + expectAudit.module('./app/helpers/capitalize.js').hasConsumers(['./app/components/one-person.hbs']); }); test('component is consumed only from the template that uses it', function () { - expectAudit.module('./components/one-person.js').hasConsumers(['./templates/people/show.hbs']); + expectAudit.module('./app/components/one-person.js').hasConsumers(['./app/templates/people/show.hbs']); }); test('modifier is consumed only from the template that uses it', function () { - expectAudit.module('./modifiers/auto-focus.js').hasConsumers(['./templates/people/edit.hbs']); + expectAudit.module('./app/modifiers/auto-focus.js').hasConsumers(['./app/templates/people/edit.hbs']); }); test('does not include unused component', function () { - expectAudit.module('./components/unused.hbs').doesNotExist(); + expectAudit.module('./app/components/unused.hbs').doesNotExist(); }); }); }); @@ -444,19 +446,19 @@ splitScenarios }); test('helper is consumed only from the template that uses it', function () { - expectAudit.module('./helpers/capitalize.js').hasConsumers(['./components/one-person.hbs']); + expectAudit.module('./app/helpers/capitalize.js').hasConsumers(['./app/components/one-person.hbs']); }); test('component is consumed only from the template that uses it', function () { - expectAudit.module('./components/one-person.js').hasConsumers(['./pods/people/show/template.hbs']); + expectAudit.module('./app/components/one-person.js').hasConsumers(['./app/pods/people/show/template.hbs']); }); test('modifier is consumed only from the template that uses it', function () { - expectAudit.module('./modifiers/auto-focus.js').hasConsumers(['./pods/people/edit/template.hbs']); + expectAudit.module('./app/modifiers/auto-focus.js').hasConsumers(['./app/pods/people/edit/template.hbs']); }); test('does not include unused component', function () { - expectAudit.module('./components/unused.hbs').doesNotExist(); + expectAudit.module('./app/components/unused.hbs').doesNotExist(); }); }); }); @@ -620,19 +622,19 @@ splitScenarios }); test('helper is consumed only from the template that uses it', function () { - expectAudit.module('./helpers/capitalize.js').hasConsumers(['./components/one-person.hbs']); + expectAudit.module('./app/helpers/capitalize.js').hasConsumers(['./app/components/one-person.hbs']); }); test('component is consumed only from the template that uses it', function () { - expectAudit.module('./components/one-person.js').hasConsumers(['./routes/people/show/template.hbs']); + expectAudit.module('./app/components/one-person.js').hasConsumers(['./app/routes/people/show/template.hbs']); }); test('modifier is consumed only from the template that uses it', function () { - expectAudit.module('./modifiers/auto-focus.js').hasConsumers(['./routes/people/edit/template.hbs']); + expectAudit.module('./app/modifiers/auto-focus.js').hasConsumers(['./app/routes/people/edit/template.hbs']); }); test('does not include unused component', function () { - expectAudit.module('./components/unused.hbs').doesNotExist(); + expectAudit.module('./app/components/unused.hbs').doesNotExist(); }); }); }); diff --git a/tests/scenarios/compat-stage2-test.ts b/tests/scenarios/compat-stage2-test.ts index 53b6ceb0a..8663049b3 100644 --- a/tests/scenarios/compat-stage2-test.ts +++ b/tests/scenarios/compat-stage2-test.ts @@ -24,7 +24,7 @@ function resolveEntryPoint(expectAudit: ReturnType) { .module('./index.html') .resolves(/\/index.html.*/) // in-html app-boot script .toModule() - .resolves(/\/app\.js.*/) + .resolves(/\/app\/app\.js.*/) .toModule() .resolves(/.*\/-embroider-entrypoint.js/) .toModule(); @@ -563,7 +563,7 @@ stage2Scenarios }); test('index.hbs', function (assert) { - let expectModule = expectAudit.module('./templates/index.hbs'); + let expectModule = expectAudit.module('./app/templates/index.hbs'); // explicit dependency expectModule @@ -606,7 +606,7 @@ stage2Scenarios }); test('curly.hbs', function (assert) { - let expectModule = expectAudit.module('./templates/curly.hbs'); + let expectModule = expectAudit.module('./app/templates/curly.hbs'); expectModule .resolves(/my-addon\/_app_\/components\/hello-world/) .toModule() @@ -635,7 +635,7 @@ stage2Scenarios test('app/hello-world.js', function (assert) { expectAudit - .module('./templates/index.hbs') + .module('./app/templates/index.hbs') .resolves(/my-addon\/_app_\/components\/hello-world/) .toModule() .withContents(contents => { @@ -655,7 +655,7 @@ stage2Scenarios test('addon/hello-world.js', function (assert) { const expectModule = expectAudit - .module('./templates/index.hbs') + .module('./app/templates/index.hbs') .resolves(/my-addon\/_app_\/components\/hello-world/) .toModule() .resolves(/my-addon\/components\/hello-world\.js/) // remapped to precise copy of my-addon @@ -692,7 +692,7 @@ stage2Scenarios test('app/templates/components/direct-template-reexport.js', function (assert) { expectAudit - .module('./templates/index.hbs') + .module('./app/templates/index.hbs') .resolves(/my-addon\/_app_\/templates\/components\/direct-template-reexport\.js\/-embroider-pair-component/) .toModule() .resolves(/my-addon\/_app_\/templates\/components\/direct-template-reexport\.js/) @@ -707,7 +707,7 @@ stage2Scenarios test('uses-inline-template.js', function (assert) { expectAudit - .module('./components/uses-inline-template.js') + .module('./app/components/uses-inline-template.js') .resolves(/\/components\/first-choice.hbs\/-embroider-pair-component/) .toModule() .withContents(contents => { @@ -720,7 +720,7 @@ stage2Scenarios test('component with relative import of arbitrarily placed template', function () { expectAudit - .module(/\/app\.js.*/) + .module(/\/app\/app\.js.*/) .resolves(/.*\/-embroider-entrypoint\.js/) .toModule() .resolves(/.*\/-embroider-implicit-modules\.js/) @@ -734,7 +734,7 @@ stage2Scenarios test('app can import a deep addon', function () { expectAudit - .module('./use-deep-addon.js') + .module('./app/use-deep-addon.js') .resolves(/deep-addon\/index.js/) .toModule() .codeContains('export default function () {}'); @@ -771,27 +771,27 @@ stage2Scenarios }); test(`app's babel plugins ran`, async function () { - let assertFile = expectFile('custom-babel-needed.js').transform(build.transpile); + let assertFile = expectFile('app/custom-babel-needed.js').transform(build.transpile); assertFile.matches(/console\.log\(['"]embroider-sample-transforms-result['"]\)/); }); test('dynamic import is preserved', function () { - expectFile('./does-dynamic-import.js') + expectFile('./app/does-dynamic-import.js') .transform(build.transpile) .matches(/return import\(['"]some-library['"]\)/); }); test('hbs transform sees expected module name', function () { - let assertFile = expectFile('templates/components/module-name-check/index.hbs').transform(build.transpile); + let assertFile = expectFile('app/templates/components/module-name-check/index.hbs').transform(build.transpile); assertFile.matches( - '"my-app/templates/components/module-name-check/index.hbs"', + '"my-app/app/templates/components/module-name-check/index.hbs"', 'our sample transform injected the expected moduleName into the compiled template' ); }); test('non-static other paths are included in the entrypoint', function (assert) { resolveEntryPoint(expectAudit).withContents(contents => { - const result = /import \* as (\w+) from "\/non-static-dir\/another-library.js";/.exec(contents); + const result = /import \* as (\w+) from "\/app\/non-static-dir\/another-library.js";/.exec(contents); if (!result) { throw new Error('Could not find import for non-static-dir/another-library'); @@ -900,7 +900,7 @@ dummyAppScenarios }); test('dummy app sees that its being developed', function () { - let assertFile = expectFile('../../tmp/rewritten-app/components/inside-dummy-app.js').transform( + let assertFile = expectFile('../../tmp/rewritten-app/app/components/inside-dummy-app.js').transform( build.transpile ); assertFile.matches(/console\.log\(true\)/); diff --git a/tests/scenarios/compat-template-colocation-test.ts b/tests/scenarios/compat-template-colocation-test.ts index 02e4a0c39..b1862cfcc 100644 --- a/tests/scenarios/compat-template-colocation-test.ts +++ b/tests/scenarios/compat-template-colocation-test.ts @@ -158,7 +158,7 @@ module('Integration | Component | addon-component-one', function (hooks) { expectAudit, contents => { assert.ok( - /import TEMPLATE from ['"]\/components\/has-colocated-template.hbs.*['"];/.test(contents), + /import TEMPLATE from ['"]\/app\/components\/has-colocated-template.hbs.*['"];/.test(contents), 'imported template' ); assert.ok(/import \{ setComponentTemplate \}/.test(contents), 'found setComponentTemplate'); @@ -176,7 +176,7 @@ module('Integration | Component | addon-component-one', function (hooks) { expectAudit, contents => { assert.ok( - /import TEMPLATE from ['"]\/components\/template-only-component.hbs.*['"];/.test(contents), + /import TEMPLATE from ['"]\/app\/components\/template-only-component.hbs.*['"];/.test(contents), 'imported template' ); assert.ok(/import \{ setComponentTemplate \}/.test(contents), 'found setComponentTemplate'); @@ -193,7 +193,7 @@ module('Integration | Component | addon-component-one', function (hooks) { test(`app's colocated components are implicitly included correctly`, function (assert) { checkContents(expectAudit, contents => { - const result = /import \* as (\w+) from "\/components\/has-colocated-template.js.*";/.exec(contents); + const result = /import \* as (\w+) from "\/app\/components\/has-colocated-template.js.*";/.exec(contents); if (!result) { console.log(contents); @@ -392,12 +392,12 @@ appScenarios .module('./index.html') .resolves(/\/index.html.*/) // in-html app-boot script .toModule() - .resolves(/\/app\.js.*/) + .resolves(/\/app\/app\.js.*/) .toModule() .resolves(/.*\/-embroider-entrypoint.js/) .toModule() .withContents(content => { - let result = /import \* as (\w+) from "\/components\/pod-component\/component\.js"/.exec(content); + let result = /import \* as (\w+) from "\/app\/components\/pod-component\/component\.js"/.exec(content); if (!result) { throw new Error('Could not find pod component'); @@ -409,7 +409,7 @@ appScenarios 'expected module is in the export list' ); - result = /import \* as (\w+) from "\/components\/pod-component\/template\.hbs.*"/.exec(content); + result = /import \* as (\w+) from "\/app\/components\/pod-component\/template\.hbs.*"/.exec(content); if (!result) { throw new Error('Could not find pod component template'); @@ -421,7 +421,7 @@ appScenarios 'expected module is in the export list' ); - result = /import \* as (\w+) from "\/components\/template-only\/template\.hbs.*"/.exec(content); + result = /import \* as (\w+) from "\/app\/components\/template-only\/template\.hbs.*"/.exec(content); if (!result) { throw new Error('Could not find template only component'); diff --git a/tests/scenarios/core-resolver-test.ts b/tests/scenarios/core-resolver-test.ts index 764cc19b3..583dbd575 100644 --- a/tests/scenarios/core-resolver-test.ts +++ b/tests/scenarios/core-resolver-test.ts @@ -30,6 +30,10 @@ Scenarios.fromProject(() => new Project()) name: 'my-app', keywords: ['ember-addon'], 'ember-addon': appMeta as any, + exports: { + './*': './*', + './tests/*': './tests/*', + }, }; app.mergeFiles({ 'index.html': '', diff --git a/tests/ts-app-template/app/index.html b/tests/ts-app-template/app/index.html index 8fcb46225..3c632d832 100644 --- a/tests/ts-app-template/app/index.html +++ b/tests/ts-app-template/app/index.html @@ -18,8 +18,8 @@ diff --git a/tests/ts-app-template/package.json b/tests/ts-app-template/package.json index 090209d6c..2b35c5725 100644 --- a/tests/ts-app-template/package.json +++ b/tests/ts-app-template/package.json @@ -11,7 +11,8 @@ "test": "tests" }, "exports": { - "./*": "./*" + "./tests/*": "./tests/*", + "./*": "./app/*" }, "scripts": { "build": "ember build --environment=production",