@webdiscus/pug-loader
Advanced tools
Comparing version 1.6.4 to 1.7.0-beta.0
# Change log | ||
## 1.7.0 (2022-02-07) | ||
- possible BREAKING CHANGE (low probability): limiting for the method `compile` by resolving a variable in the argument of require() used in pug, see [resolve resources](https://github.com/webdiscus/pug-loader#resolve_resources) .\ | ||
The methods `render` and `html` are not affected. | ||
- change the evaluation to interpolation of required files for the `compile` method () to fix issue `undefined variable` | ||
- fix issue `undefined variable` for method `compile` by use the variables in pug w/o optional chaining | ||
- added tests for the `compile` and `render` methods | ||
- update packages | ||
## 1.6.4 (2022-01-31) | ||
@@ -4,0 +12,0 @@ - added supports the `htmlWebpackPlugin.options` in pug template, #8 |
{ | ||
"name": "@webdiscus/pug-loader", | ||
"version": "1.6.4", | ||
"version": "1.7.0-beta.0", | ||
"description": "The pug loader resolves paths and webpack aliases in a pug template and compiles it to HTML or into a template function.", | ||
@@ -73,17 +73,17 @@ "keywords": [ | ||
"devDependencies": { | ||
"@babel/core": "^7.16.12", | ||
"@babel/core": "^7.17.0", | ||
"@babel/preset-env": "^7.16.11", | ||
"@types/jest": "^27.4.0", | ||
"picocolors": "^1.0.0", | ||
"css-loader": "^6.5.1", | ||
"css-loader": "^6.6.0", | ||
"html-loader": "^3.1.0", | ||
"html-webpack-plugin": "^5.5.0", | ||
"jest": "^27.4.7", | ||
"jest": "^27.5.0", | ||
"jstransformer-markdown-it": "^2.1.0", | ||
"prettier": "^2.5.1", | ||
"pug-plugin": "^1.2.4", | ||
"pug-plugin": "^1.3.0-beta.0", | ||
"rimraf": "^3.0.2", | ||
"tsconfig-paths-webpack-plugin": "^3.5.2", | ||
"webpack": "^5.67.0" | ||
"webpack": "^5.68.0" | ||
} | ||
} |
112
README.md
@@ -497,3 +497,3 @@ <div align="center"> | ||
For processing image resources in templates with webpack use the `require()` function: | ||
To handle resources in pug with webpack use the `require()` function: | ||
@@ -504,3 +504,61 @@ ```pug | ||
To handles embedded resources in pug add the webpack module `asset/resource`: | ||
<a id="resolve_resources" name="resolve_resources" href="#resolve_resources"></a> | ||
> ###💡 Resolve resources | ||
> - in the current directory the path `MUST` start with `./`: | ||
> ```pug | ||
> img(src=require('./image.jpeg')) | ||
> img(src=require('./sub/path/to/image.jpeg')) | ||
> ``` | ||
> - in the parent directory the path `MUST` start with `../`: | ||
> ```pug | ||
> img(src=require('../images/image.jpeg')) | ||
> ``` | ||
> - in the directory defined in `option.base` the path `MUST` start with `/`: | ||
> ```pug | ||
> img(src=require('/src/assets/images/image.jpeg')) | ||
> ``` | ||
> - in the directory defined by `webpack aliase` the path `MAY` start with `~` or `@`, e.g. with the alias `Images: path.join(__dirname, 'src/assets/images/')`: | ||
> ```pug | ||
> img(src=require('Images/image.jpeg')) | ||
> img(src=require('~Images/image.jpeg')) | ||
> img(src=require('@Images/image.jpeg')) | ||
> ``` | ||
> - ⚠️ using a variable with the `compile` method has a limitation: the variable `MUST NOT` contain a path, only a filename, because is interpolated at compile time: | ||
> ```pug | ||
> - const file = 'image.jpeg' | ||
> img(src=require('./path/to/' + file)) // sub directory | ||
> img(src=require('../path/to/' + file)) // parent directory | ||
> img(src=require('/path/to/' + file)) // option.base directory | ||
> img(src=require('~Images/' + file)) // webpack alias | ||
> ``` | ||
> but in current directory, the filename `MUST` start with `./`: | ||
> ```pug | ||
> - const file = './image.jpeg' | ||
> img(src=require(file)) | ||
> ``` | ||
> - ⚠️ using a alias from the `paths` defined in `tsconfig.json` with the `compile` method has a limitation: the required argument `MUST` be a string only, the webpack not supports an expression with alias:\ | ||
> tsconfig.json | ||
> ```js | ||
> { | ||
> "compilerOptions": { | ||
> "paths": { | ||
> "@Images/*": ["assets/images/*"] | ||
> } | ||
> } | ||
> } | ||
> ``` | ||
> ```pug | ||
> - const file = './image.jpeg' | ||
> img(src=require('@Images/image.jpeg')) // webpack alias resolved via `resolve.plugiins` from `tsconfig.json` | ||
> img(src=require('@Images/' + file)) // ERROR: Can't resolve '@Images' in require expression. | ||
> ``` | ||
> - using a variable with `render` and `html` methods has no limitation: the variable `MAY` contain a path, because is resolved at runtime: | ||
> ```pug | ||
> - const file = '../parent/path/to/image.jpeg' | ||
> img(src=require(file)) | ||
> img(src=require('~Images/' + file)) | ||
> img(src=require('@Images/' + file)) | ||
> ``` | ||
To handles images in pug add the webpack module `asset/resource`: | ||
```js | ||
@@ -511,3 +569,3 @@ module.exports = { | ||
{ | ||
test: /\.(png|jpg|jpeg)/, | ||
test: /\.(png|jpg|jpeg|svg|ico)/, | ||
type: 'asset/resource', | ||
@@ -522,2 +580,20 @@ generator: { | ||
``` | ||
To handles fonts in pug add the webpack module `asset/resource`: | ||
```js | ||
module.exports = { | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.(eot|ttf|woff|woff2)/, | ||
type: 'asset/resource', | ||
generator: { | ||
filename: 'assets/fonts/[name][ext]', | ||
}, | ||
}, | ||
] | ||
}, | ||
}; | ||
``` | ||
More information about asset-modules [see here](https://webpack.js.org/guides/asset-modules/). | ||
@@ -529,3 +605,3 @@ | ||
each file in files | ||
img(src=require(file)) | ||
img(src=require(`./path/to/${file})`) | ||
``` | ||
@@ -539,3 +615,3 @@ | ||
alias: { | ||
Images: path.join(__dirname, 'src/images/'), | ||
Images: path.join(__dirname, 'src/assets/images/'), | ||
}, | ||
@@ -545,15 +621,15 @@ } | ||
| Code | @webdiscus/<br>pug-loader | pugjs/<br>pug-loader | | ||
|----------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------|-----------------------------------------| | ||
| `img(src=require('image.jpeg'))` | <span style="color:green">**OK**</span> | <span style="color:red">fail</span> | | ||
| `img(src=require('./image.jpeg'))` | <span style="color:green">**OK**</span> | <span style="color:green">**OK**</span> | | ||
| `img(src=require('../images/image.jpeg'))` | <span style="color:green">**OK**</span> | <span style="color:green">**OK**</span> | | ||
| `img(src=require('Images/image.jpeg'))` | <span style="color:green">**OK**</span> | <span style="color:green">**OK**</span> | | ||
| `- var file = 'image.jpeg'`<br>``img(src=require(`Images/${file}`))`` | <span style="color:green">**OK**</span> | <span style="color:green">**OK**</span> | | ||
| `- var file = './image.jpeg'`<br>`img(src=require(file))` | <span style="color:green">**OK**</span> | <span style="color:red">fail</span> | | ||
| `- var file = 'images/image.jpeg'`<br>`img(src=require(file))` | <span style="color:green">**OK**</span> | <span style="color:red">fail</span> | | ||
| `- var file = '../images/image.jpeg'`<br>`img(src=require(file))` | <span style="color:green">**OK**</span> | <span style="color:red">fail</span> | | ||
| `- var file = 'image.jpeg'`<br>``img(src=require(`./images/${file}`))`` | <span style="color:green">**OK**</span> | <span style="color:green">**OK**</span> | | ||
| `- var file = 'image.jpeg'`<br>`img(src=require('../images/' + file))` | <span style="color:green">**OK**</span> | <span style="color:green">**OK**</span> | | ||
| the `pugjs/pug-loader` can't resolve when used a mixin and require on same file: <br> `include mixins`<br>`img(src=require('./image.jpeg'))` | <span style="color:green">**OK**</span> | <span style="color:red">fail</span> | | ||
| Example in pug template | @webdiscus/<br>pug-loader<br>`render` / `html` methods | @webdiscus/<br>pug-loader<br>`compile` method | pugjs/<br>pug-loader | | ||
|--------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|-----------------------------------------------|-----------------------| | ||
| `img(src=require('image.jpeg'))` | ✅ but not recomended | ❌ | ❌ | | ||
| `img(src=require('./image.jpeg'))` | ✅ | ✅ | ✅ | | ||
| `img(src=require('../images/image.jpeg'))` | ✅ | ✅ | ✅ | | ||
| `img(src=require('~Images/image.jpeg'))` | ✅ | ✅ | ✅ | | ||
| `- var file = 'image.jpeg'`<br>``img(src=require(`~Images/${file}`))`` | ✅ | ✅ | ✅ | | ||
| `- var file = './image.jpeg'`<br>`img(src=require(file))` | ✅ | ✅ | ❌ | | ||
| `- var file = './images/image.jpeg'`<br>`img(src=require(file))` | ✅ | ❌ | ❌ | | ||
| `- var file = '../images/image.jpeg'`<br>`img(src=require(file))` | ✅ | ❌ | ❌ | | ||
| `- var file = 'image.jpeg'`<br>``img(src=require(`./images/${file}`))`` | ✅ | ✅ | ✅ | | ||
| `- var file = 'image.jpeg'`<br>`img(src=require('../images/' + file))` | ✅ | ✅ | ✅ | | ||
| `pugjs/pug-loader` can't resolve a resource<br>when used a mixin and require in same file: <br> `include mixins`<br>`img(src=require('./image.jpeg'))` | ✅ | ✅ | ❌ | | ||
@@ -560,0 +636,0 @@ |
@@ -141,3 +141,3 @@ // add polyfill for node.js >= 12.0.0 && < 15.0.0 | ||
funcBody = Object.keys(locals).length ? injectExternalVariables(pugResult.body, locals) : pugResult.body, | ||
result = loaderMethod.run(loaderContext.resourcePath, funcBody, locals, esModule); | ||
result = loaderMethod.export(loaderContext.resourcePath, funcBody, locals, esModule); | ||
@@ -144,0 +144,0 @@ callback(null, result); |
@@ -11,25 +11,61 @@ /** | ||
/** | ||
* @type {LoaderResolver} | ||
*/ | ||
let loaderResolver; | ||
const loader = { | ||
/** | ||
* @type {LoaderResolver} | ||
*/ | ||
resolver: {}, | ||
/** | ||
* Array of required files with method compile. | ||
* @type {string[]} | ||
*/ | ||
let assetFiles = []; | ||
/** | ||
* @param {LoaderResolver} resolver | ||
*/ | ||
setResolver: (resolver) => { | ||
loader.resolver = resolver; | ||
}, | ||
/** | ||
* Normalize filename in require() function for method `compile`. | ||
* | ||
* @param {string} file The resource file. | ||
* @param {string} templateFile The template file. | ||
* @returns {string} | ||
*/ | ||
const compileRequire = (file, templateFile) => { | ||
const resolvedFile = loaderResolver.resolve(file, templateFile); | ||
assetFiles.push(resolvedFile); | ||
/** | ||
* Loader methods for returning a result. | ||
* @type {LoaderMethod[]} | ||
*/ | ||
methods: [ | ||
{ | ||
// compile into template function and export a JS module | ||
method: 'compile', | ||
queryParam: 'pug-compile', | ||
return `require('${resolvedFile}')`; | ||
requireResource: (file, templateFile) => { | ||
const resolvedFile = loader.resolver.interpolate(file, templateFile); | ||
return `require(${resolvedFile})`; | ||
}, | ||
export: (templateFile, funcBody, locals, esModule) => { | ||
return funcBody + ';' + getExportCode(esModule) + 'template;'; | ||
}, | ||
}, | ||
{ | ||
// render into HTML and export a JS module | ||
method: 'render', | ||
queryParam: 'pug-render', | ||
requireResource: (file, templateFile) => `__PUG_LOADER_REQUIRE__(${file}, '${templateFile}')`, | ||
export: (templateFile, funcBody, locals, esModule) => { | ||
let result = runTemplateFunction(funcBody, locals, renderRequire, templateFile) | ||
.replace(/\n/g, '\\n') | ||
.replace(/'/g, "\\'") | ||
.replace(/\\u0027/g, "'"); | ||
return getExportCode(esModule) + "'" + result + "';"; | ||
}, | ||
}, | ||
{ | ||
// render into HTML and return the pure string | ||
// notes: | ||
// - this method require an additional loader, like `html-loader`, to handle HTML string | ||
// - the require() function for embedded resources must be removed to allow handle the `src` in `html-loader` | ||
method: 'html', | ||
queryParam: null, | ||
requireResource: (file, templateFile) => `__PUG_LOADER_REQUIRE__(${file}, '${templateFile}')`, | ||
export: (templateFile, funcBody, locals) => runTemplateFunction(funcBody, locals, htmlRequire, templateFile), | ||
}, | ||
], | ||
}; | ||
@@ -46,3 +82,3 @@ | ||
const renderRequire = (file, templateFile) => { | ||
let resolvedFile = loaderResolver.resolve(file, templateFile); | ||
const resolvedFile = loader.resolver.resolve(file, templateFile); | ||
@@ -59,3 +95,3 @@ return `\\u0027 + require(\\u0027${resolvedFile}\\u0027) + \\u0027`; | ||
*/ | ||
const htmlRequire = (file, templateFile) => loaderResolver.resolve(file, templateFile); | ||
const htmlRequire = (file, templateFile) => loader.resolver.resolve(file, templateFile); | ||
@@ -90,65 +126,2 @@ /** | ||
const loader = { | ||
/** | ||
* @param {LoaderResolver} resolver | ||
*/ | ||
setResolver: (resolver) => { | ||
loaderResolver = resolver; | ||
}, | ||
/** | ||
* Loader methods for returning a result. | ||
* @type {LoaderMethod[]} | ||
*/ | ||
methods: [ | ||
{ | ||
// compile into template function and export a JS module | ||
method: 'compile', | ||
queryParam: 'pug-compile', | ||
requireResource: (file, templateFile) => `__PUG_LOADER_REQUIRE__(${file}, '${templateFile}')`, | ||
run: (templateFile, funcBody, locals, esModule) => { | ||
if (~funcBody.indexOf('__PUG_LOADER_REQUIRE__(')) { | ||
let index = 0; | ||
assetFiles = []; | ||
// execute the code to evaluate required path with variables | ||
// and save resolved filenames in the cache for replacing in the template function | ||
runTemplateFunction(funcBody, locals, compileRequire, templateFile); | ||
// replace the required variable filename with resolved file | ||
funcBody = funcBody.replaceAll(/__PUG_LOADER_REQUIRE__\(.+?\)/g, () => `require('${assetFiles[index++]}')`); | ||
} | ||
return funcBody + ';' + getExportCode(esModule) + 'template;'; | ||
}, | ||
}, | ||
{ | ||
// render into HTML and export a JS module | ||
method: 'render', | ||
queryParam: 'pug-render', | ||
requireResource: (file, templateFile) => `__PUG_LOADER_REQUIRE__(${file}, '${templateFile}')`, | ||
run: (templateFile, funcBody, locals, esModule) => { | ||
let result = runTemplateFunction(funcBody, locals, renderRequire, templateFile) | ||
.replace(/\n/g, '\\n') | ||
.replace(/'/g, "\\'") | ||
.replace(/\\u0027/g, "'"); | ||
return getExportCode(esModule) + "'" + result + "';"; | ||
}, | ||
}, | ||
{ | ||
// render into HTML and return the pure string | ||
// notes: | ||
// - this method require an additional loader, like `html-loader`, to handle HTML string | ||
// - the require() function for embedded resources must be removed to allow handle the `src` in `html-loader` | ||
method: 'html', | ||
queryParam: null, | ||
requireResource: (file, templateFile) => `__PUG_LOADER_REQUIRE__(${file}, '${templateFile}')`, | ||
run: (templateFile, funcBody, locals) => runTemplateFunction(funcBody, locals, htmlRequire, templateFile), | ||
}, | ||
], | ||
}; | ||
module.exports = loader; |
@@ -16,27 +16,2 @@ // the 'enhanced-resolve' package already used in webpack, don't need to define it in package.json | ||
/** | ||
* Resolve an alias in the argument of require() function. | ||
* | ||
* @param {string} value The value of extends/include/require(). | ||
* @param {{}} aliases The `resolve.alias` of webpack config. | ||
* @return {string | false} If found an alias return resolved normalized path otherwise return false. | ||
*/ | ||
const resolveAlias = (value, aliases) => { | ||
if (!aliases) return false; | ||
const patternAliases = Object.keys(aliases).join('|'); | ||
// webpack.alias is empty | ||
if (!patternAliases) return false; | ||
const [, alias] = new RegExp(aliasRegexp(patternAliases)).exec(value) || []; | ||
// path contains no alias | ||
if (!alias) return false; | ||
let resolvedFile = value.replace(new RegExp(aliasRegexp(alias)), aliases[alias]); | ||
return path.join(resolvedFile); | ||
}; | ||
/** | ||
* @param {string} path The start path to resolve. | ||
@@ -69,4 +44,8 @@ * @param {{}} options The enhanced-resolve options. | ||
* @typedef {Object} LoaderResolver | ||
* @property {string} [basedir = '/'] | ||
* @property {Object} [aliases = {}] | ||
* @property {function(basedir:string, path:string, options:{})} init | ||
* @property {(function(context:string, file:string): string)} resolve | ||
* @property {(function(file:string, context:string): string)} resolve | ||
* @property {(function(file:string, context:string): string)} interpolate | ||
* @property {(function(value:string, aliases:{}=): string)} resolveAlias | ||
* @property {function(templateFile:string, value:string, dependencies:string[]): string} resolveRequireCode | ||
@@ -76,4 +55,2 @@ * @property {function(templateFile:string, value:string, method:LoaderMethod): string} resolveRequireResource | ||
let pugLoaderBasedir = '/'; | ||
let aliases = null; | ||
let resolveFile = null; | ||
@@ -85,2 +62,4 @@ | ||
const resolver = { | ||
basedir: '/', | ||
/** | ||
@@ -92,4 +71,4 @@ * @param {string} basedir The the root directory of all absolute inclusion. | ||
init: (basedir, path, options) => { | ||
pugLoaderBasedir = basedir; | ||
aliases = options.alias; | ||
resolver.basedir = basedir; | ||
resolver.aliases = options.alias; | ||
resolveFile = getFileResolverSync(path, options); | ||
@@ -107,11 +86,11 @@ }, | ||
const context = path.dirname(templateFile); | ||
let resolvedPath; | ||
let resolvedPath = null; | ||
// resolve an absolute path by prepending options.basedir | ||
if (file[0] === '/') { | ||
resolvedPath = path.join(pugLoaderBasedir, file); | ||
resolvedPath = path.join(resolver.basedir, file); | ||
} | ||
// resolve a relative file | ||
if (!resolvedPath && file[0] === '.') { | ||
if (resolvedPath == null && file[0] === '.') { | ||
resolvedPath = path.join(context, file); | ||
@@ -121,8 +100,8 @@ } | ||
// resolve a file by webpack `resolve.alias` | ||
if (!resolvedPath) { | ||
resolvedPath = resolveAlias(file, aliases); | ||
if (resolvedPath == null) { | ||
resolvedPath = resolver.resolveAlias(file, resolver.aliases); | ||
} | ||
// fallback to enhanced resolver | ||
if (!resolvedPath) { | ||
if (resolvedPath == null) { | ||
try { | ||
@@ -141,2 +120,80 @@ resolvedPath = resolveFile(context, file); | ||
/** | ||
* Interpolate filename for `compile` method. | ||
* | ||
* @note: the file is the argument of require() and can be any expression, like require('./' + file + '.jpg'). | ||
* See https://webpack.js.org/guides/dependency-management/#require-with-expression. | ||
* | ||
* @param {string} file The file to resolve. | ||
* @param {string} templateFile The template file. | ||
* @return {string} | ||
*/ | ||
interpolate: (file, templateFile) => { | ||
file = file.trim(); | ||
const quote = file[0]; | ||
let resolvedPath = null; | ||
// the argument begin with a string quote | ||
if ('\'"`'.indexOf(quote) >= 0) { | ||
const context = path.dirname(templateFile) + '/'; | ||
// resolve an absolute path by prepending options.basedir | ||
if (file[1] === '/') { | ||
resolvedPath = file[0] + resolver.basedir + file.substring(2); | ||
} | ||
// resolve a relative file | ||
// fix the issue when the required file has a relative path (`./` or `../`) and is in an included file | ||
if (resolvedPath == null && file.substring(1, 4) === '../') { | ||
resolvedPath = file[0] + context + file.substring(1); | ||
} | ||
// resolve a relative file | ||
if (resolvedPath == null && file.substring(1, 3) === './') { | ||
resolvedPath = file[0] + context + file.substring(3); | ||
} | ||
// resolve a webpack `resolve.alias` | ||
if (resolvedPath == null) { | ||
resolvedPath = resolver.resolveAlias(file.substring(1)); | ||
if (resolvedPath) resolvedPath = file[0] + resolvedPath; | ||
} | ||
if (isWin) resolvedPath = pathToPosix(resolvedPath); | ||
} else { | ||
// fix webpack require issue `Cannot find module` for the case: | ||
// - var file = './image.jpeg'; | ||
// require(file) <- error | ||
// require(file + '') <- solution | ||
file += " + ''"; | ||
} | ||
return resolvedPath || file; | ||
}, | ||
/** | ||
* Resolve an alias in the argument of require() function. | ||
* | ||
* @param {string} value The value of extends/include/require(). | ||
* @param {{}} [aliases = resolver.aliases] The `resolve.alias` of webpack config. | ||
* @return {string | null} If found an alias return resolved normalized path otherwise return false. | ||
*/ | ||
resolveAlias: (value, aliases = resolver.aliases) => { | ||
if (!aliases) return null; | ||
const patternAliases = Object.keys(aliases).join('|'); | ||
// webpack.alias is empty | ||
if (!patternAliases) return null; | ||
const [, alias] = new RegExp(aliasRegexp(patternAliases)).exec(value) || []; | ||
// path contains no alias | ||
if (!alias) return null; | ||
let resolvedFile = value.replace(new RegExp(aliasRegexp(alias)), aliases[alias]); | ||
return path.join(resolvedFile); | ||
}, | ||
/** | ||
* Resolve the source path in require(). | ||
@@ -143,0 +200,0 @@ * |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
61429
656
769
1
1