Skip to content

Commit

Permalink
Add ES module output (#65)
Browse files Browse the repository at this point in the history
* Add `esModule` option
* Convert loader sources to ES modules
  • Loading branch information
ogonkov authored Jul 5, 2020
1 parent 87ce2a9 commit 57eb77e
Show file tree
Hide file tree
Showing 20 changed files with 132 additions and 63 deletions.
15 changes: 14 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,20 @@ module.exports = {
// preset: null,

// Run tests from one or more projects
// projects: null,
projects: [
{
displayName: 'cjs',
globals: {
__USE_ES__: false
}
},
{
displayName: 'es',
globals: {
__USE_ES__: true
}
}
],

// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
Expand Down
2 changes: 2 additions & 0 deletions src/ast/get-assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {StaticExtension} from '../static-extension/StaticExtension';
* 'foo' + bar + 'qux'
*
* @param {nunjucks.nodes.Add} node
*
* @returns {string}
*/
function getAddNodeValue(node) {
if (!(node instanceof nunjucks.nodes.Add)) {
Expand Down
16 changes: 11 additions & 5 deletions src/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {toAssetsUUID} from './output/to-assets-uuid';
import {ERROR_MODULE_NOT_FOUND, TEMPLATE_DEPENDENCIES} from './constants';
import {getTemplateImports} from './output/get-template-imports';
import {ASSETS_KEY} from './static-extension/contants';
import {getModuleOutput} from './output/get-module-output';

export default function nunjucksLoader(source) {
const isWindows = process.platform === 'win32';
Expand Down Expand Up @@ -61,7 +62,7 @@ export default function nunjucksLoader(source) {
isAsyncTemplate
});
callback(null, `
${getTemplateImports(this, {
${getTemplateImports(this, options.esModule, {
assets: assetsUUID,
dependencies,
extensions,
Expand All @@ -70,8 +71,8 @@ export default function nunjucksLoader(source) {
})}
${precompiled}
exports = module.exports = function nunjucksTemplate(ctx = {}) {
var nunjucks = runtime(
function nunjucksTemplate(ctx = {}) {
var nunjucks = (${getModuleOutput('runtime')})(
${envOptions},
${TEMPLATE_DEPENDENCIES}
);
Expand All @@ -85,8 +86,13 @@ export default function nunjucksLoader(source) {
return nunjucks.render(${resourcePathString}, ctx);
};
exports.__nunjucks_precompiled_template__ = ${TEMPLATE_DEPENDENCIES}.templates[${resourcePathString}];
exports.${TEMPLATE_DEPENDENCIES} = ${TEMPLATE_DEPENDENCIES};
nunjucksTemplate.__nunjucks_precompiled_template__ = ${TEMPLATE_DEPENDENCIES}.templates[${resourcePathString}];
nunjucksTemplate.${TEMPLATE_DEPENDENCIES} = ${TEMPLATE_DEPENDENCIES};
${options.esModule ?
'export default' :
'exports = module.exports ='
} nunjucksTemplate;
`);
}, function(error) {
if (error.code === ERROR_MODULE_NOT_FOUND &&
Expand Down
7 changes: 4 additions & 3 deletions src/output/get-assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {getDynamicPathRegexp} from './get-dynamic-path-regexp';

/**
* @param {Array.<string[]>} assets
* @returns {{imports: function(Object, boolean): string}}
*/
export function getAssets(assets) {
function imports(loaderContext) {
function imports(loaderContext, esModule) {
return assets.map(function([uuid, assetPath, assetImport]) {
const args = getArgs(assetPath);
const isDynamicImport = assetImport.startsWith('"');
Expand Down Expand Up @@ -38,9 +39,9 @@ export function getAssets(assets) {
);
const importInvocation = isDynamicImport ?
`const ${importVar} = function(${args.join()}) {
return ${getImportStr(importPath, true)()}
return ${getImportStr(importPath, esModule, true)()}
};` :
`${getImportStr(importPath)(importVar)}`;
`${getImportStr(importPath, esModule)(importVar)}`;

return `
${importInvocation}
Expand Down
5 changes: 3 additions & 2 deletions src/output/get-extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import {IMPORTS_PREFIX, TEMPLATE_DEPENDENCIES} from '../constants';
import {getModuleOutput} from './get-module-output';

export function getExtensions(extensions) {
function imports(loaderContext) {
function imports(loaderContext, esModule) {
return extensions.map(([name, importPath]) => {
const importVar = toVar(`${IMPORTS_PREFIX}_ext_${name}`);
const importStatement = getImportStr(
stringifyRequest(loaderContext, importPath)
stringifyRequest(loaderContext, importPath),
esModule
)(importVar);

return `
Expand Down
3 changes: 2 additions & 1 deletion src/output/get-filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import {toVar} from '../utils/to-var';
import {getModuleOutput} from './get-module-output';

export function getFilters(filters) {
function imports(loaderContext) {
function imports(loaderContext, esModule) {
return filters.map(([filterName, importPath, filterInstance]) => {
const importVar = toVar(`${IMPORTS_PREFIX}_filter_${filterName}`);
const importStatement = getImportStr(
stringifyRequest(loaderContext, importPath),
esModule
)(importVar);

return `
Expand Down
5 changes: 3 additions & 2 deletions src/output/get-globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import {toVar} from '../utils/to-var';
import {getModuleOutput} from './get-module-output';

export function getGlobals(globals) {
function imports(loaderContext) {
function imports(loaderContext, esModule) {
return globals.map(([globalImport, globalPath]) => {
const importVar = toVar(`${IMPORTS_PREFIX}_global_${globalImport}`);
const importStatement = getImportStr(
stringifyRequest(loaderContext,globalPath)
stringifyRequest(loaderContext,globalPath),
esModule
)(importVar);

return `
Expand Down
4 changes: 2 additions & 2 deletions src/output/get-runtime-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import {stringifyRequest} from 'loader-utils';
import path from 'path';
import {getImportStr} from '../utils/get-import-str';

export function getRuntimeImport(loaderContext) {
export function getRuntimeImport(loaderContext, esModule) {
const runtimePath = stringifyRequest(
loaderContext,
`${path.resolve(path.join(__dirname, '..', 'runtime.js'))}`
);

return `${getImportStr(runtimePath)('runtime')}`
return `${getImportStr(runtimePath, esModule)('runtime')}`
}
10 changes: 6 additions & 4 deletions src/output/get-template-dependencies-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ function getImports(imports, assignments) {

function foldDependenciesToImports(
loaderContext,
esModule,
[imports, assignment],
[, fullPath],
i
Expand All @@ -43,7 +44,7 @@ function foldDependenciesToImports(
return [
`
${imports}
${getImportStr(path)(importVar)}
${getImportStr(path, esModule)(importVar)}
`,
{
templates: join('templates'),
Expand All @@ -65,13 +66,14 @@ function foldDependenciesToImports(
* ...dep0.__nunjucks_module_dependencies__.templates
* };
*
* @param loaderContext
* @param {Object} loaderContext
* @param {boolean} esModule
* @param {Array<string[]>} dependencies
* @returns {string}
*/
export function getTemplateDependenciesImport(loaderContext, dependencies) {
export function getTemplateDependenciesImport(loaderContext, esModule, dependencies) {
return getImports(
...dependencies.reduce(foldDependenciesToImports.bind(null, loaderContext), ['', {
...dependencies.reduce(foldDependenciesToImports.bind(null, loaderContext, esModule), ['', {
templates: '',
globals: '',
extensions: '',
Expand Down
14 changes: 7 additions & 7 deletions src/output/get-template-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import {getAssets} from './get-assets';
import {getExtensions} from './get-extensions';
import {getFilters} from './get-filters';

export function getTemplateImports(loader, {
export function getTemplateImports(loader, esModule, {
assets,
dependencies,
extensions,
filters,
globals
}) {
return `
${getRuntimeImport(loader)}
${getTemplateDependenciesImport(loader, dependencies)}
${getGlobals(globals).imports(loader)}
${getAssets(assets).imports(loader)}
${getExtensions(extensions).imports(loader)}
${getFilters(filters).imports(loader)}
${getRuntimeImport(loader, esModule)}
${getTemplateDependenciesImport(loader, esModule, dependencies)}
${getGlobals(globals).imports(loader, esModule)}
${getAssets(assets).imports(loader, esModule)}
${getExtensions(extensions).imports(loader, esModule)}
${getFilters(filters).imports(loader, esModule)}
`;
}
1 change: 0 additions & 1 deletion src/precompile/configure-environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import nunjucks from 'nunjucks';
import {getModule} from '../utils/get-module';

/**
* @param env
* @param {Object} options
* @param {string[]} options.searchPaths
* @param {Object} options.options
Expand Down
8 changes: 4 additions & 4 deletions src/runtime.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const {WebpackPrecompiledLoader} = require('./WebpackPrecompiledLoader');
import {WebpackPrecompiledLoader} from './WebpackPrecompiledLoader';

const nunjucks = require('nunjucks/browser/nunjucks-slim');
import nunjucks from 'nunjucks/browser/nunjucks-slim';

module.exports = function runtime(options, {
export default function runtime(options, {
globals,
extensions,
filters,
Expand Down Expand Up @@ -71,4 +71,4 @@ module.exports = function runtime(options, {
);
}
};
};
}
4 changes: 4 additions & 0 deletions src/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@
}
},
"default": {}
},
"esModule": {
"type": "boolean",
"default": false
}
},
"additionalProperties": false
Expand Down
17 changes: 16 additions & 1 deletion src/utils/get-import-str.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
/**
* @param {string} path
* @param {boolean} [esModule]
* @param {boolean} [isDynamic]
* @returns {function(string, string): string}
*/
export function getImportStr(path, isDynamic) {
export function getImportStr(path, esModule, isDynamic) {
const isES = esModule === true;

/**
* @param {string} [name] Variable name to be used
* @param {string} [sym] Module symbol to be imported
* @returns {string}
*/
function getImportInvocationString(name, sym) {
if (isDynamic && isES) {
return `import(${path});`;
}

if (name && sym && isES) {
return `import {${sym} as ${name}} from ${path};`;
}

if (name && isES) {
return `import ${name} from ${path};`;
}

if (name && sym) {
return `const ${name} = require(${path}).${sym};`
}
Expand Down
22 changes: 3 additions & 19 deletions src/utils/get-possible-paths.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,15 @@
import path from 'path';
import {unquote} from './unquote';

function getFilePath(searchPath, possiblePath) {
const [firstPart, ...restParts] = possiblePath.split(' + ');
const filePath = path.resolve(searchPath, unquote(firstPart));

return restParts.length > 0 ?
`${[`"${filePath}"`, ...restParts].join(' + ')}` : filePath;
}
import {resolveSearchPaths} from './resolve-search-paths';

/**
* @param {string[]} paths
* @param {string[]} searchPaths
* @returns {Array.<[string, array]>}
* @returns {Array.<[string, string[]]>}
*/
export function getPossiblePaths(paths, searchPaths) {
return paths.map(function(possiblePath) {
return [
possiblePath,
searchPaths.map((searchPath) => [
path.resolve(searchPath),
getFilePath(searchPath, possiblePath)
]).filter(function([basePath, filePath]) {
return unquote(filePath).startsWith(basePath);
}).map(function([, filePath]) {
return filePath;
})
resolveSearchPaths(possiblePath, searchPaths)
];
});
}
2 changes: 1 addition & 1 deletion src/utils/get-string-base64-hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/**
* @link https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
* @param str
* @param {string} str
* @returns {*}
*/
function getStringHash(str) {
Expand Down
26 changes: 26 additions & 0 deletions src/utils/resolve-search-paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import path from 'path';
import {unquote} from './unquote';

function getFilePath(searchPath, possiblePath) {
const [firstPart, ...restParts] = possiblePath.split(' + ');
const filePath = path.resolve(searchPath, unquote(firstPart));

return restParts.length > 0 ?
`${[`"${filePath}"`, ...restParts].join(' + ')}` : filePath;
}

/**
* @param {string} possiblePath
* @param {string[]} searchPaths
* @returns {string[]}
*/
export function resolveSearchPaths(possiblePath, searchPaths) {
return searchPaths.map((searchPath) => [
path.resolve(searchPath),
getFilePath(searchPath, possiblePath)
]).filter(function([basePath, filePath]) {
return unquote(filePath).startsWith(basePath);
}).map(function([, filePath]) {
return filePath;
})
}
1 change: 1 addition & 0 deletions test/assets.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import compiler from './compiler';

const loaderOptions = {
esModule: __USE_ES__,
assetsPaths: [
'test/fixtures/django_project/app_example/static'
]
Expand Down
8 changes: 5 additions & 3 deletions test/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export default (fixture, options = {}) => {
entry: `./${fixture}`,
output: {
libraryTarget: 'commonjs2',
path: path.resolve(__dirname),
filename: `bundles/${bundleName}.js`,
path: path.join(__dirname, 'bundles'),
filename: `${bundleName}.js`,
},
module: {
rules: [{
Expand Down Expand Up @@ -46,7 +46,9 @@ export default (fixture, options = {}) => {
reject(new Error(stats.toJson().errors));
}

resolve(require(`./bundles/${bundleName}.js`));
import(`./bundles/${bundleName}.js`).then(function(module) {
resolve(module.default || module);
});
});
});
}
Loading

0 comments on commit 57eb77e

Please sign in to comment.