forked from instructure/canvas-lms
-
Notifications
You must be signed in to change notification settings - Fork 0
/
i18nLinerHandlebars.js
154 lines (135 loc) · 5.44 KB
/
i18nLinerHandlebars.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// 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.
import Handlebars from 'handlebars'
import {pick} from 'lodash'
import {EmberHandlebars} from 'ember-template-compiler'
import ScopedHbsExtractor from './../gems/canvas_i18nliner/js/scoped_hbs_extractor'
import {allFingerprintsFor} from 'brandable_css/lib/main'
import _PreProcessor from './../gems/canvas_i18nliner/node_modules/i18nliner-handlebars/dist/lib/pre_processor'
const PreProcessor = _PreProcessor.default
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 result = precompiler.precompile(ast).toString()
const payload = {template: result, scope: scope, translationCount: translationCount}
return payload
}
catch (e) {
e = e.message || e
console.log(e)
throw {error: e}
}
}
const emitTemplate = (path, name, result, dependencies, cssRegistration, partialRegistration) => {
const moduleName = `jst/${path.replace(/.*\/\jst\//, '').replace(/\.handlebars/, '')}`
return `
define('${moduleName}', ${JSON.stringify(dependencies)}, function(Handlebars){
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
var name = '${name}';
templates[name] = template(${result['template']});
${partialRegistration}
${cssRegistration}
return templates[name];
});
`
}
const resourceName = (path) => {
return 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) => {
return 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 `
var brandableCss = arguments[1];
brandableCss.loadStylesheet('${bundle}', ${options});
`
}
const partialRegexp = /\{\{>\s?\[?(.+?)\]?( .*?)?}}/g
const findReferencedPartials = (source) => {
let 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
}
export default function i18nLinerHandlebarsLoader (source) {
this.cacheable()
const name = resourceName(this.resourcePath)
const dependencies = ['handlebars']
var partialRegistration = emitPartialRegistration(this.resourcePath, name)
var cssRegistration = buildCssReference(name)
if (cssRegistration){
// arguments[1] will be brandableCss
dependencies.push('compiled/util/brandableCss')
}
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)
}
var compiledTemplate = emitTemplate(this.resourcePath, name, result, dependencies, cssRegistration, partialRegistration)
return compiledTemplate
}