UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

123 lines (119 loc) 3.56 kB
import '../../debug/index.js'; import { assert } from '../../debug/lib/assert.js'; class StrictResolver { // Ember's router uses this flag to decide whether to auto-generate // `${name}_loading` and `${name}_error` substates for routes defined in // `Router.map(...)`. Since we always resolve against an ES module registry, // we unconditionally opt in. moduleBasedResolver = true; #modules = new Map(); original; static create({ namespace }) { (!(namespace.modules) && assert(`<Application#modules> must be set when using the StrictResolver`, namespace.modules)); return new StrictResolver(namespace.modules); } constructor(modules) { this.addModules(modules); } addModules(modules) { for (let [moduleName, module] of Object.entries(modules)) { this.#modules.set(this.#normalizeModule(moduleName), module); } } #normalizeModule(moduleName) { return moduleName.replace(fileExtension, '').replace(leadingDotSlash, ''); } #plural(word) { if (word === 'config') { return word; } return word + 's'; } resolve(fullName) { let [type, name] = fullName.split(':'); name = this.#normalizeName(type, name); for (let strategy of [this.#resolveSelf, this.#mainLookup, this.#defaultLookup, this.#nestedColocationLookup]) { let result = strategy.call(this, type, name); if (result) { return this.#extractDefaultExport(result.hit); } } return undefined; } #extractDefaultExport(module) { if (module && module['default']) { module = module['default']; } return module; } normalize(fullName) { let [type, name] = fullName.split(':'); name = this.#normalizeName(type, name); return `${type}:${name}`; } #normalizeName(type, name) { if (type === 'component' || type === 'helper' || type === 'modifier' || type === 'template' && name.indexOf('components/') === 0) { return name.replace(/_/g, '-'); } else { return dasherize(name.replace(/\./g, '/')); } } #resolveSelf(type, name) { if (type === 'resolver' && name === 'current') { return { hit: { create: () => this } }; } return undefined; } #mainLookup(type, name) { if (name === 'main') { let module = this.#modules.get(type); if (module) { return { hit: module }; } } return undefined; } #defaultLookup(type, name) { let dir = this.#plural(type); let target = `${dir}/${name}`; let module = this.#modules.get(target); if (module) { return { hit: module }; } return undefined; } // Supports the nested colocation pattern where `component:my-widget` // resolves to `./components/my-widget/index.{js,ts,gjs,gts}`. The index // file is typically the component class, and it's commonly paired with a // sibling `index.hbs` inside the same folder. #nestedColocationLookup(type, name) { if (type !== 'component') return undefined; let dir = this.#plural(type); let target = `${dir}/${name}/index`; let module = this.#modules.get(target); if (module) { return { hit: module }; } return undefined; } } const fileExtension = /\.\w{1,4}$/; const leadingDotSlash = /^\.\//; const camelCaseBoundary = /([a-z\d])([A-Z])/g; const spacesAndUnderscores = /[ _]/g; function dasherize(str) { return str.replace(camelCaseBoundary, '$1_$2').toLowerCase().replace(spacesAndUnderscores, '-'); } export { StrictResolver };