Skip to content

Instantly share code, notes, and snippets.

@lifeart
Created February 21, 2019 16:02
Show Gist options
  • Save lifeart/58f74c89812c420bcb3044857732292b to your computer and use it in GitHub Desktop.
Save lifeart/58f74c89812c420bcb3044857732292b to your computer and use it in GitHub Desktop.
Octane Template Imports Over ember-template-component-import
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);
}
});
'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