forked from meteor/meteor
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Remove Blaze dependencies from static-html (meteor#10267)
These changes create a new copy of the static-html and caching-html-compiler packages in core, as well as a new package called html-scanner, to house the html-scanner.js functionality from the templating-tools package. With these changes in place, we're able to remove all Blaze dependencies from static-html, which benefits React based Meteor apps. We don't need the extra `CompileError` class, and using it was throwing off `caching-html-compiler` error handling. Errors with messages and line numbers weren't being interpreted / formatted properly.
- Loading branch information
Showing
11 changed files
with
804 additions
and
9 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# caching-html-compiler | ||
|
||
Provides a pluggable class used to compile HTML-style templates in Meteor build | ||
plugins. This abstracts out a lot of the functionality you would need to | ||
implement the following plugins: | ||
|
||
1. `templating` | ||
2. `static-html` | ||
3. `simple:markdown-templating` | ||
|
||
It provides automatic caching and handles communicating with the build plugin | ||
APIs. The actual functions that convert HTML into compiled form are passed in | ||
as arguments into the constructor, allowing those functions to be unit tested | ||
separately from the caching and file system functionality. | ||
|
||
------- | ||
|
||
### new CachingHtmlCompiler(name, tagScannerFunc, tagHandlerFunc) | ||
|
||
Constructs a new CachingHtmlCompiler that can be passed into | ||
`Plugin.registerCompiler`. | ||
|
||
#### Arguments | ||
|
||
1. `name` The name of the compiler, used when printing errors. Should probably | ||
be the same as the name of the build plugin and package it is used in. | ||
2. `tagScannerFunc` A function that takes a string representing a template | ||
file as input, and returns an array of Tag objects. See the README for | ||
`templating-tools` for more information about the Tag object. | ||
3. `tagHandlerFunc` A function that takes an array of Tag objects (the output | ||
of the previous argument) and returns an object with `js`, `body`, `head`, | ||
and `bodyAttr` properties, which will be added to the app through the build | ||
plugin API. | ||
|
||
#### Example | ||
|
||
Here is some example code from the `templating` package: | ||
|
||
```js | ||
Plugin.registerCompiler({ | ||
extensions: ['html'], | ||
archMatching: 'web', | ||
isTemplate: true | ||
}, () => new CachingHtmlCompiler( | ||
"templating", | ||
TemplatingTools.scanHtmlForTags, | ||
TemplatingTools.compileTagsWithSpacebars | ||
)); | ||
``` |
147 changes: 147 additions & 0 deletions
147
packages/caching-html-compiler/caching-html-compiler.js
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
const path = Plugin.path; | ||
|
||
// The CompileResult type for this CachingCompiler is the return value of | ||
// htmlScanner.scan: a {js, head, body, bodyAttrs} object. | ||
CachingHtmlCompiler = class CachingHtmlCompiler extends CachingCompiler { | ||
/** | ||
* Constructor for CachingHtmlCompiler | ||
* @param {String} name The name of the compiler, printed in errors - | ||
* should probably always be the same as the name of the build | ||
* plugin/package | ||
* @param {Function} tagScannerFunc Transforms a template file (commonly | ||
* .html) into an array of Tags | ||
* @param {Function} tagHandlerFunc Transforms an array of tags into a | ||
* results object with js, body, head, and bodyAttrs properties | ||
*/ | ||
constructor(name, tagScannerFunc, tagHandlerFunc) { | ||
super({ | ||
compilerName: name, | ||
defaultCacheSize: 1024*1024*10, | ||
}); | ||
|
||
this._bodyAttrInfo = null; | ||
|
||
this.tagScannerFunc = tagScannerFunc; | ||
this.tagHandlerFunc = tagHandlerFunc; | ||
} | ||
|
||
// Implements method from CachingCompilerBase | ||
compileResultSize(compileResult) { | ||
function lengthOrZero(field) { | ||
return field ? field.length : 0; | ||
} | ||
return lengthOrZero(compileResult.head) + lengthOrZero(compileResult.body) + | ||
lengthOrZero(compileResult.js); | ||
} | ||
|
||
// Overrides method from CachingCompiler | ||
processFilesForTarget(inputFiles) { | ||
this._bodyAttrInfo = {}; | ||
super.processFilesForTarget(inputFiles); | ||
} | ||
|
||
// Implements method from CachingCompilerBase | ||
getCacheKey(inputFile) { | ||
// Note: the path is only used for errors, so it doesn't have to be part | ||
// of the cache key. | ||
return inputFile.getSourceHash(); | ||
} | ||
|
||
// Implements method from CachingCompiler | ||
compileOneFile(inputFile) { | ||
const contents = inputFile.getContentsAsString(); | ||
const inputPath = inputFile.getPathInPackage(); | ||
|
||
// Since we can't control node_modules based HTML content, we'll skip | ||
// over it here to avoid trying to handle HTML files that don't | ||
// fit within Meteor's HTML file handling rules (e.g. Meteor doesn't | ||
// allow files that have a DOCTYPE specified, since it adds its own). | ||
if (!inputPath.startsWith('node_modules')) { | ||
try { | ||
const tags = this.tagScannerFunc({ | ||
sourceName: inputPath, | ||
contents: contents, | ||
tagNames: ["body", "head", "template"] | ||
}); | ||
return this.tagHandlerFunc(tags); | ||
} catch (e) { | ||
if (e.message && e.line) { | ||
inputFile.error({ | ||
message: e.message, | ||
line: e.line | ||
}); | ||
return null; | ||
} else { | ||
throw e; | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Implements method from CachingCompilerBase | ||
addCompileResult(inputFile, compileResult) { | ||
let allJavaScript = ""; | ||
|
||
if (compileResult.head) { | ||
inputFile.addHtml({ section: "head", data: compileResult.head }); | ||
} | ||
|
||
if (compileResult.body) { | ||
inputFile.addHtml({ section: "body", data: compileResult.body }); | ||
} | ||
|
||
if (compileResult.js) { | ||
allJavaScript += compileResult.js; | ||
} | ||
|
||
if (Object.keys(compileResult.bodyAttrs).length !== 0) { | ||
Object.keys(compileResult.bodyAttrs).forEach((attr) => { | ||
const value = compileResult.bodyAttrs[attr]; | ||
if (this._bodyAttrInfo.hasOwnProperty(attr) && | ||
this._bodyAttrInfo[attr].value !== value) { | ||
// two conflicting attributes on <body> tags in two different template | ||
// files | ||
inputFile.error({ | ||
message: | ||
`<body> declarations have conflicting values for the '${ attr }' ` + | ||
`attribute in the following files: ` + | ||
this._bodyAttrInfo[attr].inputFile.getPathInPackage() + | ||
`, ${ inputFile.getPathInPackage() }` | ||
}); | ||
} else { | ||
this._bodyAttrInfo[attr] = {inputFile, value}; | ||
} | ||
}); | ||
|
||
// Add JavaScript code to set attributes on body | ||
allJavaScript += | ||
`Meteor.startup(function() { | ||
var attrs = ${JSON.stringify(compileResult.bodyAttrs)}; | ||
for (var prop in attrs) { | ||
document.body.setAttribute(prop, attrs[prop]); | ||
} | ||
}); | ||
`; | ||
} | ||
|
||
|
||
if (allJavaScript) { | ||
const filePath = inputFile.getPathInPackage(); | ||
// XXX this path manipulation may be unnecessarily complex | ||
let pathPart = path.dirname(filePath); | ||
if (pathPart === '.') | ||
pathPart = ''; | ||
if (pathPart.length && pathPart !== path.sep) | ||
pathPart = pathPart + path.sep; | ||
const ext = path.extname(filePath); | ||
const basename = path.basename(filePath, ext); | ||
|
||
// XXX generate a source map | ||
|
||
inputFile.addJavaScript({ | ||
path: path.join(pathPart, "template." + basename + ".js"), | ||
data: allJavaScript | ||
}); | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Package.describe({ | ||
name: 'caching-html-compiler', | ||
summary: "Pluggable class for compiling HTML into templates", | ||
version: '1.1.3', | ||
git: 'https://github.com/meteor/meteor.git' | ||
}); | ||
|
||
Package.onUse(function (api) { | ||
api.use([ | ||
'caching-compiler', | ||
'ecmascript' | ||
]); | ||
|
||
api.export('CachingHtmlCompiler', 'server'); | ||
|
||
api.addFiles([ | ||
'caching-html-compiler.js' | ||
], 'server'); | ||
}); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# html-scanner | ||
|
||
## `scanHtmlForTags(options)` | ||
|
||
Scan an HTML file for top-level tags as specified by `options.tagNames`, and | ||
return an array of `Tag` objects. | ||
|
||
### Options | ||
|
||
1. `sourceName` the name of the input file, used when throwing errors. | ||
2. `contents` the contents of the input file, these are parsed to find the | ||
top-level tags. | ||
3. `tagNames` the top-level tags to look for in the HTML. | ||
|
||
### Example | ||
|
||
```js | ||
const tags = scanHtmlForTags({ | ||
sourceName: inputPath, | ||
contents: contents, | ||
tagNames: ["body", "head", "template"] | ||
}); | ||
``` | ||
|
||
### Tag object | ||
|
||
```js | ||
{ | ||
// Name of the tag - "body", "head", "template", etc | ||
tagName: String, | ||
|
||
// Attributes on the tag | ||
attribs: { [attrName]: String }, | ||
|
||
// Contents of the tag | ||
contents: String, | ||
|
||
// Starting index of the opening tag in the source file | ||
// (used to throw informative errors) | ||
tagStartIndex: Number, | ||
|
||
// Starting index of the contents of the tag in the source file | ||
// (used to throw informative errors) | ||
contentsStartIndex: Number, | ||
|
||
// The contents of the entire source file, should be used only to | ||
// throw informative errors (for example, this can be used to | ||
// determine the line number for an error) | ||
fileContents: String, | ||
|
||
// The file name of the initial source file, used to throw errors | ||
sourceName: String | ||
}; | ||
``` |
Oops, something went wrong.