Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
spence-s committed Jan 31, 2021
1 parent 4dd7908 commit 2a2e00b
Show file tree
Hide file tree
Showing 6 changed files with 2,273 additions and 114 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ coverage
# Files #
###################
*.log

dist
96 changes: 79 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# better-pug-loader
# simple-pug-loader

[![build status](https://img.shields.io/travis/com/Spence-S/better-pug-loader.svg)](https://travis-ci.com/Spence-S/better-pug-loader)
[![code coverage](https://img.shields.io/codecov/c/github/Spence-S/better-pug-loader.svg)](https://codecov.io/gh/Spence-S/better-pug-loader)
Expand All @@ -8,58 +8,120 @@
[![license](https://img.shields.io/github/license/Spence-S/better-pug-loader.svg)](LICENSE)
[![npm downloads](https://img.shields.io/npm/dt/better-pug-loader.svg)](https://npm.im/better-pug-loader)

> Pug loader for webpack that's better than the original
> Pug template loader for webpack that's better and more straight forward than the original
## Table of Contents

* [Install](#install)
* [Usage](#usage)
* [Contributors](#contributors)
* [License](#license)
- [Why Another Pug Loader](#why-another-pug-loader)
- [Install](#install)
- [Usage](#usage)
- [Contributors](#contributors)
- [License](#license)

## Why Another Pug Loader

The defacto loader for compiling pug templates is [`pug-loader`](https://github.com/pugjs/pug-loader).

`simple-pug-loader` does the same thing but better for many use cases.

Pug loader, while it can work well, has not been well maintained. It suffers from many issues that cause it to not respect normal pug scoping rules
and it also handles includes and mixins clunkily. It uses its own - somewhat complicated - way of parsing out includes and mixins and then using webpack requires to get the code loaded into the module correctly.
While this works satisfactorily many times, with more complexities in the pug code, you end up running into a lot of weird and unexpected issues with the front end
templates it creates and weird bugs arise from where variables aren't being passed to included templates and mixins properly. Because of the odd way it requires
some instances of pug templates, templates don't respond and rebuild correctly as expected when watching using `webpack --watch`. You can see a lot of the same gripes
over and over in the [issues](https://github.com/pugjs/pug-loader/issues).

This loader is just a simple wrapper around pugs `compileClientWithDependenciesTracked` function. The loader tells webpack
to track any dependencies using pugs own methods of tracking rather than monkey patching it with a custom function/pug plugin like `pug-loader` does. Then we give webpack
the list of dependencies for the file, so it never gets confused on what to build when a file changes.

We only a slight change to pugs algorithm when an `include` is not another pug file, but rather a different file type altogether.
This is called a `raw include` under the hood. In the case of raw includes, we then tell webpack require the raw module independently of pug as a string.
This embeds the raw file right in the pug template as a string as is intended in most cases and the file is tracked as a dependencie properly in webpack.

While I haven't seen the exact reasons for `pug-loader` forcing webpack requires in the situations it does, I'm sure there is one and if you run across cases where this loader doesn't work for you, but `pug-loader` does, please file an issue!

I personally haven't found use cases that do not benefit from the simpler approach I am employing with this plugin yet.

My use personal use case is that I render many templates both server and client side and I want predictability from both.

### Versions
This loader will work best on Webpack 5 and likely 4. No guarantee for any lesser versions. Please file issues for webpack 4 or 5 problems.

## Install

[npm][]:

```sh
npm install better-pug-loader
npm install -d simple-pug-loader
```

[yarn][]:

```sh
yarn add better-pug-loader
yarn add -D simple-pug-loader
```


## Usage

In you webpack config.

```js
const BetterPugLoader = require('better-pug-loader');
...,
module: {
rules: [
{
test: /\.pug$/,
use: [
{
loader: 'simple-pug-loader'
}
]
}
]
},
...
```

### Options

The following [options] are available to be set for the loader. They are all mapped directly to Pug options, unless pointed out otherwise. These are all the same as `pug-loader`.

- `doctype`
- Unlike Pug, it defaults to `"html"` if not set
- `globals`
- `self`
- `plugins`
- Note that you cannot specify any Pug plugins implementing `read` or `resolve` hooks, as those are reserved for the loader
- `pretty`
- `filters`
- `root`
- webpack uses its own file resolving mechanism, so while it is functionally equivalent to the Pug option with the same name, it is implemented differently

const betterPugLoader = new BetterPugLoader();
### Embedded resources

console.log(betterPugLoader.renderName());
// script
Try to use `require` for all your embedded resources, to process them with webpack.

```pug
div
img(src=require("./my/image.png"))
```

Remember, you need to configure loaders for these file types too. You might be interested in the [file loader][file-loader].

If a non-pug resource is included with `include resource.whatever`, `simple-pug-loader` will load it as a raw string automatically.

## Contributors

| Name | Website |
| ------------------ | -------------------------- |
| **Spencer Snyder** | <https://spencersnyder.io> |


## License

[MIT](LICENSE) © [Spencer Snyder](https://spencersnyder.io)


##

[npm]: https://www.npmjs.com/

[yarn]: https://yarnpkg.com/
111 changes: 101 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,105 @@
class Script {
constructor(config) {
config = { ...config };
this._name = config.name || 'script';
const path = require('path');
const loaderUtils = require('loader-utils');
const nodeResolve = require('resolve').sync;
const dedent = require('dedent');
const walk = require('pug-walk');

this.renderName = this.renderName.bind(this);
}
const pugPath = require.resolve('pug');

const runtimePath = nodeResolve('pug-runtime', {
basedir: path.dirname(pugPath)
});

const rawLoaderPath = nodeResolve('raw-loader', { basedir: __dirname });

const pug = require(pugPath);

module.exports = function (source) {
// All the cool loaders do it
if (this.cacheable) this.cacheable(true);

// Options and context
const loaderContext = this;
const options = loaderUtils.getOptions(this) || {};
const filename = loaderContext.resourcePath;

let func;

renderName() {
return this._name;
source = typeof source === 'string' ? source : source.toString();

const plugin = {
preLink(ast) {
return walk(ast, (node, replace) => {
if (
node.type === 'RawInclude' &&
node.file &&
path.extname(node.file.fullPath) !== '.pug'
) {
const val = `require(${loaderUtils.stringifyRequest(
loaderContext,
rawLoaderPath + '?esModule=false!' + node.file.fullPath
)})`;

replace({
type: 'Code',
val,
buffer: true,
mustEscape: false,
isInline: false,
line: node.line,
filename: node.filename
});
}
});
}
};

try {
const pugOptions = {
filename,
doctype: options.doctype || 'html',
pretty: options.pretty,
self: options.self,
compileDebug: loaderContext.debug || false,
globals: ['require'].concat(options.globals || []),
name: 'template',
inlineRuntimeFunctions: false,
filters: options.filters,
plugins: [plugin].concat(options.plugins || [])
};

// Compile the pug
const compilation = pug.compileClientWithDependenciesTracked(
source,
pugOptions
);

func = compilation.body;

// Let webpack know to watch the dependencies
if (compilation.dependencies && compilation.dependencies.length > 0)
compilation.dependencies.forEach((dep) =>
loaderContext.addDependency(dep)
);
} catch (error) {
// Catch errors if needed
loaderContext.callback(error);
return;
}
}

module.exports = Script;
// Add the runtime dependency
const requireRuntimeString =
'var pug = require(' +
loaderUtils.stringifyRequest(loaderContext, '!' + runtimePath) +
');\n\n';

// Return the compiled function to be processes as a JS module now
loaderContext.callback(
null,
dedent`
${requireRuntimeString}
${func.toString()}
module.exports = template;
`
);
};
41 changes: 26 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "better-pug-loader",
"description": "Pug loader for webpack that's better than the original",
"name": "simple-pug-loader",
"description": "Pug loader for webpack that's more simple than the original",
"version": "0.0.0",
"author": "Spencer Snyder <[email protected]> (https://spencersnyder.io)",
"bugs": {
Expand All @@ -10,25 +10,33 @@
"contributors": [
"Spencer Snyder <[email protected]> (https://spencersnyder.io)"
],
"dependencies": {},
"dependencies": {
"dedent": "^0.7.0",
"file-type": "^16.2.0",
"loader-utils": "^2.0.0",
"pug-walk": "^2.0.0",
"raw-loader": "^4.0.2",
"resolve": "^1.19.0"
},
"devDependencies": {
"@commitlint/cli": "latest",
"@commitlint/config-conventional": "latest",
"ava": "latest",
"codecov": "latest",
"clean-webpack-plugin": "^3.0.0",
"cross-env": "latest",
"eslint": "latest",
"eslint-config-xo-lass": "latest",
"fixpack": "latest",
"husky": "latest",
"lint-staged": "latest",
"memfs": "^3.2.0",
"nyc": "latest",
"remark-cli": "latest",
"remark-preset-github": "latest",
"webpack": "^5.17.0",
"webpack-cli": "^4.4.0",
"webpack-dev-server": "^3.11.2",
"xo": "latest"
},
"engines": {
"node": ">= 10"
"node": ">= 12"
},
"homepage": "https://github.com/Spence-S/better-pug-loader",
"keywords": [
Expand All @@ -37,6 +45,7 @@
],
"license": "MIT",
"main": "index.js",
"peerDependencies": {},
"repository": {
"type": "git",
"url": "https://github.com/Spence-S/better-pug-loader"
Expand All @@ -45,16 +54,18 @@
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"lint": "yarn run lint:js && yarn run lint:md",
"lint:js": "xo",
"lint:md": "remark . -qfo",
"pretest": "yarn run lint",
"test": "cross-env NODE_ENV=test ava",
"test-coverage": "cross-env NODE_ENV=test nyc yarn run test"
"lint:md": "remark . -qfo"
},
"xo": {
"prettier": true,
"space": true,
"extends": [
"xo-lass"
]
"settings": {
"import/resolver": {
"node": {}
}
},
"rules": {
"unicorn/prevent-abbreviations": "off"
}
}
}
27 changes: 0 additions & 27 deletions test/test.js

This file was deleted.

Loading

0 comments on commit 2a2e00b

Please sign in to comment.