Created
February 21, 2019 16:02
-
-
Save lifeart/58f74c89812c420bcb3044857732292b to your computer and use it in GitHub Desktop.
Octane Template Imports Over ember-template-component-import
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getOwner } from "@ember/application"; | |
import Helper from "@ember/component/helper"; | |
function generateHash(module, testName) { | |
var str = module + "\x1C" + testName; | |
var hash = 0; | |
for (var i = 0; i < str.length; i++) { | |
hash = (hash << 5) - hash + str.charCodeAt(i); | |
hash |= 0; | |
} | |
// Convert the possibly negative integer hash code into an 8 character hex string, which isn't | |
// strictly necessary but increases user understanding that the id is a SHA-like hash | |
var hex = (0x100000000 + hash).toString(16); | |
if (hex.length < 8) { | |
hex = "0000000" + hex; | |
} | |
return hex.slice(-8); | |
} | |
function getAppName(appInstance) { | |
if (appInstance.base && appInstance.base.name) { | |
return appInstance.base.name; | |
} | |
// TODO: would this work in 2.4+? | |
return ( | |
appInstance.application.name || | |
appInstance.application.modulePrefix || | |
appInstance.application.__registry__.resolver._configRootName || | |
"dummy" | |
); | |
} | |
function registerAbsoluteImport(templateFileName, originalImport, ctx) { | |
console.log('originalImport-start', templateFileName, originalImport); | |
const templateId = 'imp-' + generateHash(templateFileName, originalImport).slice(0, 5); // f34f3 | |
const appName = getAppName(getOwner(ctx)); | |
if (originalImport.startsWith('../../'+ appName)) { | |
originalImport = originalImport.replace('../../',''); | |
} | |
if (originalImport.startsWith('src')) { | |
originalImport = appName + '/' + originalImport; | |
} | |
console.log('originalImport-middle', templateFileName, originalImport); | |
const splitter = originalImport.includes('/-components') ? '/-components/' : '/components/'; | |
if (!originalImport.includes('/src/ui/')) { | |
originalImport = templateFileName.replace('template.hbs', '') + originalImport; | |
} | |
const hasOriginalImport = require.has(originalImport) || require.has(originalImport + '/component') || require.has(originalImport + '/template'); | |
if (!hasOriginalImport) { | |
const lastPath = originalImport.split('/'); | |
lastPath.pop(); | |
originalImport = lastPath.join('/'); | |
} | |
const componentName = originalImport.split(splitter)[1]; | |
const localPrefix = `${appName}/src/ui/components/${templateId}/`; | |
const absoluteComponentPath = localPrefix + componentName; | |
if (!require.has(absoluteComponentPath)) { | |
if (require.has(originalImport)) { | |
define.exports(absoluteComponentPath, window.require(originalImport)); | |
} | |
if (require.has(originalImport + '/template')) { | |
define.exports(absoluteComponentPath + '/template', window.require(originalImport + '/template')); | |
} | |
if (require.has(originalImport + '/component')) { | |
define.exports(absoluteComponentPath + '/component', window.require(originalImport + '/component')); | |
} | |
} | |
console.log('originalImport', originalImport, absoluteComponentPath); | |
return templateId + '/' + componentName; | |
} | |
export default Helper.extend({ | |
compute([one, two]) { | |
return registerAbsoluteImport(one, two, this); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/* eslint-env node */ | |
const assert = require('assert'); | |
const path = require('path'); | |
const BroccoliFilter = require('broccoli-persistent-filter'); | |
const md5Hex = require('md5-hex'); | |
const IMPORT_PATTERN = /\{\{\s*import\s+([^\s]+)\s+from\s+['"]([^'"]+)['"]\s*\}\}/gi; | |
function dasherizeName(name = '') { | |
const result = []; | |
const nameSize = name.length; | |
if (!nameSize) { | |
return ''; | |
} | |
result.push(name.charAt(0)); | |
for (let i = 1; i < nameSize; i++) { | |
let char = name.charAt(i); | |
if (char === char.toUpperCase()) { | |
if (char !== '-' && char !== '/' && char !== '_') { | |
if (result[result.length - 1] !== '-' && result[result.length - 1] !== '/') { | |
result.push('-'); | |
} | |
} | |
} | |
result.push(char); | |
} | |
return result.join(''); | |
} | |
function toLegacyImport(line) { | |
var cleanImports = line.split('import').map((item) => item.trim()).filter((text) => text.length).map(i => i.split(' from ')); | |
const components = []; | |
cleanImports.map(([left, right]) => { | |
let importSt = right.replace(/[^a-zA-Z0-9-]+/g, " ").trim().split(' ').join('/'); | |
if (!importSt.endsWith('/')) { | |
importSt = importSt + '/'; | |
} | |
if (left.includes('{')) { | |
let normalizedLeft = left.replace(/[{}]+/g, " ").trim(); | |
const statements = normalizedLeft.trim().split(',').map(name => name && name.trim()).filter(name => name.length); | |
statements.forEach((name) => { | |
const parts = name.split(' as '); | |
if (parts.length === 2) { | |
components.push([parts[1].trim(), importSt + dasherizeName(parts[0].trim()).toLowerCase()]); | |
} else { | |
components.push([name.trim(), importSt + dasherizeName(name.trim()).toLowerCase()]); | |
} | |
}); | |
} else { | |
components.push([left.trim(), importSt + dasherizeName(left.trim()).toLowerCase()]); | |
} | |
}); | |
let results = []; | |
components.forEach(([head, tail]) => { | |
results.push(`{{import ${head} from "${tail}"}}`); | |
}); | |
return results.join('\n'); | |
} | |
function isValidVariableName(name) { | |
if (!(/^[A-Za-z0-9]+$/.test(name))) { | |
return false; | |
} | |
if (name.charAt(0).toUpperCase() !== name.charAt(0)) { | |
return false; | |
} | |
return true; | |
} | |
class TemplateImportProcessor extends BroccoliFilter { | |
constructor(inputNode, options = {}) { | |
if (!options.hasOwnProperty('persist')) { | |
options.persist = true; | |
} | |
super(inputNode, { | |
annotation: options.annotation, | |
persist: options.persist | |
}); | |
this.options = options; | |
this._console = this.options.console || console; | |
this.extensions = [ 'hbs', 'handlebars' ]; | |
this.targetExtension = 'hbs'; | |
} | |
baseDir() { | |
return __dirname; | |
} | |
cacheKeyProcessString(string, relativePath) { | |
return md5Hex([ | |
string, | |
relativePath | |
]); | |
} | |
processString(contents, relativePath) { | |
const templateParts = contents.split('--- hbs ---'); | |
if (templateParts.length === 2) { | |
// toLegacyImport can be replased to `babel` parsing and anylize | |
contents = [ toLegacyImport(templateParts[0]), templateParts[1] ].join('/n'); | |
} | |
let imports = []; | |
let rewrittenContents = contents.replace(IMPORT_PATTERN, (_, localName, importPath) => { | |
if (importPath.startsWith('.')) { | |
importPath = path.resolve(relativePath, '..', importPath).split(path.sep).join('/'); | |
importPath = path.relative(this.options.root, importPath).split(path.sep).join('/'); | |
} | |
imports.push({ localName, importPath, isLocalNameValid: isValidVariableName(localName) }); | |
return ''; | |
}); | |
let header = imports.map(({ importPath, localName, isLocalNameValid }) => { | |
const warnPrefix = 'ember-template-component-import: '; | |
const abstractWarn = `${warnPrefix} Allowed import variable names - CamelCased strings, like: FooBar, TomDale`; | |
const componentWarn = ` | |
${warnPrefix}Warning! | |
in file: "${relativePath}" | |
subject: "${localName}" is not allowed as Variable name for Template import.`; | |
const warn = isLocalNameValid ? '' : ` | |
<pre data-test-name="${localName}">${componentWarn}</pre> | |
<pre data-test-global-warn="${localName}">${abstractWarn}</pre> | |
`; | |
if (!isLocalNameValid) { | |
this._console.log(componentWarn); | |
if (relativePath !== 'dummy/pods/application/template.hbs') { | |
// don't throw on 'dummy/pods/application/template.hbs' (test template) | |
throw new Error(componentWarn); | |
} | |
} | |
return `${warn}{{#let (component (register-absolute-template-import '${relativePath}' '${ importPath }')) as |${ localName }|}}`; | |
}).join(''); | |
let footer = imports.map(() => `{{/let}}`).join(''); | |
let result = header + rewrittenContents + footer; | |
return result; | |
} | |
} | |
module.exports = { | |
name: require('./package').name, | |
setupPreprocessorRegistry(type, registry) { | |
const podModulePrefix = this.project.config('development').podModulePrefix; | |
// assert.notStrictEqual( | |
// podModulePrefix, | |
// undefined, | |
// `${this.name}: 'podModulePrefix' has not been defined in config/environment.js` | |
// ); | |
registry.add('template', { | |
name: 'ember-template-component-import', | |
ext: 'hbs', | |
toTree: (tree) => { | |
let componentsRoot = path.join(this.project.root, 'src', 'ui'); | |
console.log('componentsRoot', componentsRoot); | |
tree = new TemplateImportProcessor(tree, { root: componentsRoot }); | |
return tree; | |
} | |
}); | |
if (type === 'parent') { | |
this.parentRegistry = registry; | |
} | |
}, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment