Skip to content
This repository has been archived by the owner on Mar 14, 2024. It is now read-only.

Commit

Permalink
Merge pull request #29 from ericmorand/issue_21
Browse files Browse the repository at this point in the history
Fix issue #21
  • Loading branch information
ericmorand committed Apr 8, 2021
2 parents eab9dd8 + 2509016 commit 8ad34ac
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 122 deletions.
2 changes: 1 addition & 1 deletion .nycrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
],
"include": "src",
"reporter": [
"text-summary",
"text",
"html"
]
}
7 changes: 4 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
language: node_js
node_js:
- "6"
- "7"
- "8"
- "9"
- "10"
- "11"
- "12"
- "13"
- "14"
- "15"
jobs:
include:
- stage: cover
node_js: "12"
node_js: "15"
script:
- npm run cover
- npm run coverage
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Webpack loader for Twig templates, based on [Twing](https://www.npmjs.com/packag
## Prerequisites

* Webpack 4
* Twing 3.0.1
* Twing 5.0.2

## Installation

Expand Down Expand Up @@ -65,9 +65,11 @@ module.exports = new TwingEnvironment(
```javascript
let template = require('./index.twig');

let renderedTemplate = template({
template({
foo: 'bar'
}); // "bar"
}).then((renderedTemplate) => {
// "bar"
});
```

This behavior, known as _render at runtime_, comes at the cost of having Twing as part of the bundle.
Expand Down Expand Up @@ -122,7 +124,9 @@ module.exports = new TwingEnvironment(
<sub>index.js</sub>

```javascript
let renderedTemplate = require('./index.twig'); // "bar"
require('./index.twig').then((renderedTemplate) => {
// "bar"
});
```

This second behavior, known as _render at compile time_, comes with the benefit of not having Twing as part of the bundle.
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
],
"license": "ISC",
"peerDependencies": {
"twing": "^3.0.1"
"twing": "^5.0.2"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -69,7 +69,7 @@
"tap-spec": "^5.0.0",
"tape": "^4.11.0",
"ts-node": "^8.4.1",
"twing": "^3.0.1",
"twing": "^5.0.2",
"typescript": "^3.6.3",
"webpack": "^4.41.0",
"webpack-cli": "^3.3.9"
Expand Down
48 changes: 28 additions & 20 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ const optionsSchema = {
};

class PathSupportingArrayLoader extends TwingLoaderArray {
getSourceContext(name: string, from: TwingSource): TwingSource {
let source = super.getSourceContext(name, from);

return new TwingSource(source.getCode(), source.getName(), name);
getSourceContext(name: string, from: TwingSource): Promise<TwingSource> {
return super.getSourceContext(name, from).then((source) => {
return new TwingSource(source.getCode(), source.getName(), name);
});
}
}

export default function (this: loader.LoaderContext, source: string) {
const callback = this.async();

const getTemplateHash = (name: string) => {
return this.mode !== 'production' ? name : hex.stringify(sha256(name));
};
Expand All @@ -48,13 +50,15 @@ export default function (this: loader.LoaderContext, source: string) {

validateOptions(optionsSchema, options, 'Twing loader');

delete require.cache[options.environmentModulePath];

let resourcePath: string = slash(this.resourcePath);
let environmentModulePath: string = options.environmentModulePath;
let renderContext: any = options.renderContext;

this.addDependency(slash(environmentModulePath));

// require takes module name separated wicth forward slashes
// require takes module name separated with forward slashes
let environment: TwingEnvironment = require(slash(environmentModulePath));
let loader = environment.getLoader();

Expand All @@ -71,11 +75,10 @@ export default function (this: loader.LoaderContext, source: string) {

let visitor = new Visitor(loader, resourcePath, getTemplateHash);

visitor.visit(module);
visitor.visit(module).then(() => {
let precompiledTemplate = environment.compile(module);

let precompiledTemplate = environment.compile(module);

parts.push(`let templatesModule = (() => {
parts.push(`let templatesModule = (() => {
let module = {
exports: undefined
};
Expand All @@ -86,21 +89,22 @@ ${precompiledTemplate}
})();
`);

for (let foundTemplateName of visitor.foundTemplateNames) {
// require takes module name separated with forward slashes
parts.push(`require('${slash(foundTemplateName)}');`);
}
for (let foundTemplateName of visitor.foundTemplateNames) {
// require takes module name separated with forward slashes
parts.push(`require('${slash(foundTemplateName)}');`);
}

parts.push(`env.registerTemplatesModule(templatesModule, '${key}');`);
parts.push(`env.registerTemplatesModule(templatesModule, '${key}');`);

parts.push(`
parts.push(`
let template = env.loadTemplate('${key}');
module.exports = (context = {}) => {
return template.render(context);
return template.then((template) => template.render(context));
};`);

return parts.join('\n');
callback(null, parts.join('\n'));
});
} else {
environment.setLoader(new TwingLoaderChain([
new PathSupportingArrayLoader(new Map([
Expand All @@ -109,10 +113,14 @@ module.exports = (context = {}) => {
loader
]));

environment.on('template', (name: string, from: TwingSource) => {
this.addDependency(environment.getLoader().resolve(name, from));
environment.on('template', async (name: string, from: TwingSource) => {
this.addDependency(await environment.getLoader().resolve(name, from));
});

return `module.exports = ${JSON.stringify(environment.render(resourcePath, renderContext))};`;
environment.render(resourcePath, renderContext).then((result) => {
callback(null, `module.exports = ${JSON.stringify(result)};`);
}).catch((error) => {
callback(error);
});
}
};
55 changes: 33 additions & 22 deletions src/visitor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
import {TwingLoaderInterface, TwingNode, TwingNodeExpression, TwingNodeType, TwingSource} from "twing";
import {
TwingLoaderInterface,
TwingNode,
TwingNodeExpression,
TwingNodeExpressionArray,
TwingNodeExpressionConditional,
TwingNodeExpressionConstant,
TwingNodeExpressionFunction,
TwingNodeImport,
TwingNodeInclude, TwingNodeModule,
TwingSource
} from "twing";
import {existsSync} from "fs";

const slash = require('slash');
Expand All @@ -20,11 +31,11 @@ export class Visitor {
return this._foundTemplateNames;
}

visit(node: TwingNode) {
let processExpressionNode = (node: TwingNodeExpression) => {
let pushValue = (value: string): string => {
if (this._loader.exists(value, this._from)) {
value = this._loader.resolve(value, this._from);
async visit(node: TwingNode): Promise<void> {
let processExpressionNode = async (node: TwingNodeExpression): Promise<void> => {
let pushValue = async (value: string): Promise<string> => {
if (await this._loader.exists(value, this._from)) {
value = await this._loader.resolve(value, this._from);

if (existsSync(value)) {
if (!this._foundTemplateNames.includes(value)) {
Expand All @@ -38,52 +49,52 @@ export class Visitor {
return value;
};

if (node.getType() === TwingNodeType.EXPRESSION_ARRAY) {
if (node instanceof TwingNodeExpressionArray) {
for (let [index, constantNode] of node.getNodes()) {
if ((index as number) % 2) {
processExpressionNode(constantNode);
await processExpressionNode(constantNode);
}
}
}

if (node.getType() === TwingNodeType.EXPRESSION_CONDITIONAL) {
if (node instanceof TwingNodeExpressionConditional) {
let expr2: TwingNodeExpression = node.getNode('expr2');
let expr3: TwingNodeExpression = node.getNode('expr3');

processExpressionNode(expr2);
processExpressionNode(expr3);
await processExpressionNode(expr2);
await processExpressionNode(expr3);
}

if (node.getType() === TwingNodeType.EXPRESSION_CONSTANT) {
node.setAttribute('value', pushValue(node.getAttribute('value')));
if (node instanceof TwingNodeExpressionConstant) {
node.setAttribute('value', await pushValue(node.getAttribute('value')));
}
};

// include function
if ((node.getType() === TwingNodeType.EXPRESSION_FUNCTION) && (node.getAttribute('name') === 'include')) {
processExpressionNode(node.getNode('arguments').getNode(0));
if ((node instanceof TwingNodeExpressionFunction) && (node.getAttribute('name') === 'include')) {
await processExpressionNode(node.getNode('arguments').getNode(0));
}

// import and include tags
if ((node.getType() === TwingNodeType.IMPORT) || (node.getType() === TwingNodeType.INCLUDE)) {
if ((node instanceof TwingNodeImport) || (node instanceof TwingNodeInclude)) {
if (node.hasNode('expr')) {
processExpressionNode(node.getNode('expr'));
await processExpressionNode(node.getNode('expr'));
}
}

// extends and embed tags
if ((node.getType() === TwingNodeType.MODULE)) {
if ((node instanceof TwingNodeModule)) {
if (node.hasNode('parent')) {
processExpressionNode(node.getNode('parent'))
await processExpressionNode(node.getNode('parent'))
}

for (let embeddedTemplate of node.getAttribute('embedded_templates')) {
this.visit(embeddedTemplate);
await this.visit(embeddedTemplate);
}
}

for (let [key, subNode] of node.getNodes()) {
this.visit(subNode);
for (let [, subNode] of node.getNodes()) {
await this.visit(subNode);
}
};
}
10 changes: 5 additions & 5 deletions test/integration/plugin/HtmlWebpackPlugin/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ class HtmlWebpackPluginTestCase extends TestCase {
`;
}

protected doTest(test: Test, message: string, memoryFs: MemoryFileSystem): void {
super.doTest(test, message, memoryFs);
protected doTest(test: Test, message: string, memoryFs: MemoryFileSystem): Promise<void> {
return super.doTest(test, message, memoryFs).then(() => {
let actual = memoryFs.readFileSync(resolvePath('dist/index.html'), 'UTF-8');

let actual = memoryFs.readFileSync(resolvePath('dist/index.html'), 'UTF-8');

test.same(actual, this.expectedFromPlugin, 'plugin output is valid');
test.same(actual, this.expectedFromPlugin, 'plugin output is valid');
});
}
}

Expand Down
8 changes: 4 additions & 4 deletions test/integration/test-case.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {resolve as resolvePath, relative as relativePath} from 'path';
import {resolve as resolvePath} from 'path';

import {Test} from "tape";
import * as webpack from "webpack";
Expand Down Expand Up @@ -79,17 +79,17 @@ export abstract class TestCase {

return this.compile(configuration, test)
.then((memoryFs: MemoryFileSystem) => {
this.doTest(test, this.renderContext ? 'using "render at compile time" behavior' : 'using "render at runtime" behavior', memoryFs);
return this.doTest(test, this.renderContext ? 'using "render at compile time" behavior' : 'using "render at runtime" behavior', memoryFs);
})
.catch((err) => {
test.fail(err.message);
});
}

protected doTest(test: Test, message: string, memoryFs: MemoryFileSystem): void {
protected async doTest(test: Test, message: string, memoryFs: MemoryFileSystem): Promise<void> {
let actual: string;

actual = new Function(`return ${memoryFs.readFileSync(resolvePath('dist/main.js'), 'UTF-8')};`)();
actual = await new Function(`return ${memoryFs.readFileSync(resolvePath('dist/main.js'), 'UTF-8')};`)();

test.same(actual, this.expected, message);
}
Expand Down

0 comments on commit 8ad34ac

Please sign in to comment.