195 lines
6.7 KiB
JavaScript
195 lines
6.7 KiB
JavaScript
/*
|
|
* Copyright (C) 2015 - present Instructure, Inc.
|
|
*
|
|
* This file is part of Canvas.
|
|
*
|
|
* Canvas is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU Affero General Public License as published by the Free
|
|
* Software Foundation, version 3 of the License.
|
|
*
|
|
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
// This is basically one big port of what we do in ruby-land in the
|
|
// handlebars-tasks gem. We need to run handlebars source through basic
|
|
// compilation to extract i18nliner scopes, and then we wrap the resulting
|
|
// template in an AMD module, giving it dependencies on handlebars, it's scoped
|
|
// i18n object if it needs one, and any brandableCss variant stuff it needs.
|
|
const Handlebars = require('handlebars')
|
|
const {pick} = require('lodash')
|
|
const {EmberHandlebars} = require('ember-template-compiler')
|
|
const ScopedHbsExtractor = require('i18nliner-canvas/js/scoped_hbs_extractor')
|
|
const {allFingerprintsFor} = require('brandable_css/lib/main')
|
|
const PreProcessor = require('i18nliner-handlebars/dist/lib/pre_processor').default
|
|
require('i18nliner-canvas/js/scoped_hbs_pre_processor')
|
|
|
|
// In this main file, we do a bunch of stuff to monkey-patch the default behavior of
|
|
// i18nliner's HbsProcessor (specifically, we set the the `directories` and define a
|
|
// `normalizePath` function so that translation keys stay relative to canvas root dir).
|
|
// By requiring it here the code here will use that monkeypatched behavior.
|
|
require('i18nliner-canvas/js/main')
|
|
|
|
const compileHandlebars = data => {
|
|
const path = data.path
|
|
const source = data.source
|
|
try {
|
|
let translationCount = 0
|
|
const ast = Handlebars.parse(source)
|
|
const extractor = new ScopedHbsExtractor(ast, {path})
|
|
const scope = extractor.scope
|
|
PreProcessor.scope = scope
|
|
PreProcessor.process(ast)
|
|
extractor.forEach(() => translationCount++)
|
|
|
|
const precompiler = data.ember ? EmberHandlebars : Handlebars
|
|
const template = precompiler.precompile(ast).toString()
|
|
return {template, scope, translationCount}
|
|
} catch (e) {
|
|
e = e.message || e
|
|
console.log(e)
|
|
throw {error: e}
|
|
}
|
|
}
|
|
|
|
const emitTemplate = (path, name, result, dependencies, cssRegistration, partialRegistration) => {
|
|
return `
|
|
import _Handlebars from 'handlebars/runtime';
|
|
var Handlebars = _Handlebars.default;
|
|
${dependencies.map(d => `import ${JSON.stringify(d)};`).join('\n')}
|
|
|
|
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
|
var name = '${name}';
|
|
templates[name] = template(${result.template});
|
|
${partialRegistration};
|
|
${cssRegistration};
|
|
export default templates[name];
|
|
`
|
|
}
|
|
|
|
const resourceName = path =>
|
|
path
|
|
.replace(/^.+\/app\/views\/jst\/(?:plugins\/[^\/]*\/)?/, '')
|
|
.replace(/\.handlebars$/, '')
|
|
.replace(/_/g, '-')
|
|
|
|
// given an object, returns a new object with just the 'combinedChecksum' property of each item
|
|
const getCombinedChecksums = obj =>
|
|
Object.keys(obj).reduce((accumulator, key) => {
|
|
accumulator[key] = pick(obj[key], 'combinedChecksum')
|
|
return accumulator
|
|
}, {})
|
|
|
|
const buildCssReference = name => {
|
|
const bundle = `jst/${name}`
|
|
const cached = allFingerprintsFor(`${bundle}.scss`)
|
|
const firstVariant = Object.keys(cached)[0]
|
|
if (!firstVariant) {
|
|
// no matching css file, just return a blank string
|
|
return ''
|
|
}
|
|
|
|
const options = cached[firstVariant].includesNoVariables
|
|
? // there is no branding / high contrast specific variables in this file,
|
|
// all users will use the same file.
|
|
JSON.stringify(pick(cached[firstVariant], 'combinedChecksum', 'includesNoVariables'))
|
|
: // Spit out all the combinedChecksums into the compiled js file and use brandableCss.getCssVariant()
|
|
// at runtime to determine which css variant to load, based on the user & account's settings
|
|
`${JSON.stringify(getCombinedChecksums(cached))}[brandableCss.getCssVariant()]`
|
|
|
|
return `
|
|
import brandableCss from 'compiled/util/brandableCss';
|
|
brandableCss.loadStylesheet('${bundle}', ${options});
|
|
`
|
|
}
|
|
|
|
const partialRegexp = /\{\{>\s?\[?(.+?)\]?( .*?)?}}/g
|
|
const findReferencedPartials = source => {
|
|
const partials = []
|
|
let match
|
|
while ((match = partialRegexp.exec(source))) {
|
|
partials.push(match[1].trim())
|
|
}
|
|
|
|
const uniquePartials = partials.filter((elem, pos) => partials.indexOf(elem) == pos)
|
|
|
|
return uniquePartials
|
|
}
|
|
|
|
const emitPartialRegistration = (path, resourceName) => {
|
|
const baseName = path.split('/').pop()
|
|
if (baseName.startsWith('_')) {
|
|
const partialName = baseName.replace(/^_/, '')
|
|
const partialPath = path
|
|
.replace(baseName, partialName)
|
|
.replace(/.*\/\jst\//, '')
|
|
.replace(/\.handlebars/, '')
|
|
return `
|
|
Handlebars.registerPartial('${partialPath}', templates['${resourceName}']);
|
|
`
|
|
}
|
|
return ''
|
|
}
|
|
|
|
const buildPartialRequirements = partialPaths => {
|
|
const requirements = partialPaths.map(partial => {
|
|
const partialParts = partial.split('/')
|
|
partialParts[partialParts.length - 1] = `_${partialParts[partialParts.length - 1]}`
|
|
const requirePath = partialParts.join('/')
|
|
return `jst/${requirePath}`
|
|
})
|
|
return requirements
|
|
}
|
|
|
|
function i18nLinerHandlebarsLoader(source) {
|
|
this.cacheable()
|
|
const name = resourceName(this.resourcePath)
|
|
const dependencies = []
|
|
|
|
const partialRegistration = emitPartialRegistration(this.resourcePath, name)
|
|
|
|
const cssRegistration = buildCssReference(name)
|
|
|
|
const partials = findReferencedPartials(source)
|
|
const partialRequirements = buildPartialRequirements(partials)
|
|
partialRequirements.forEach(requirement => dependencies.push(requirement))
|
|
|
|
const result = compileHandlebars({path: this.resourcePath, source})
|
|
if (result.error) {
|
|
console.log('THERE WAS AN ERROR IN PRECOMPILATION', result)
|
|
throw result
|
|
}
|
|
|
|
if (result.translationCount > 0) {
|
|
dependencies.push(`i18n!${result.scope}`)
|
|
}
|
|
|
|
// make sure the template has access to all our handlebars helpers
|
|
dependencies.push('coffeescripts/handlebars_helpers.coffee')
|
|
|
|
const compiledTemplate = emitTemplate(
|
|
this.resourcePath,
|
|
name,
|
|
result,
|
|
dependencies,
|
|
cssRegistration,
|
|
partialRegistration
|
|
)
|
|
return compiledTemplate
|
|
}
|
|
|
|
module.exports = i18nLinerHandlebarsLoader
|
|
|
|
module.exports.compile = (source, path) => {
|
|
const context = {
|
|
cacheable: () => {},
|
|
resourcePath: path
|
|
}
|
|
return i18nLinerHandlebarsLoader.call(context, source)
|
|
}
|