Security News
The Risks of Misguided Research in Supply Chain Security
Snyk's use of malicious npm packages for research raises ethical concerns, highlighting risks in public deployment, data exfiltration, and unauthorized testing.
pug-plugin
Advanced tools
The pug plugin to handle the pug, html, css, scss files in webpack entry and extract css from pug.
This plugin extract HTML and CSS from pug
html
scss
css
files defined in webpack entry
and save into separate files.
Using the pug-plugin
and pug
html
scss
css
resources in the webpack entry
no longer requires additional plugins such as:
mini-css-extract-plugin
)pug-plugin
)The plugin can be used not only for
pug
but also for simply extractingHTML
orCSS
fromwebpack entry
, independent of pug usage.
npm install pug-plugin --save-dev
The minimal configuration in webpack.config.js
:
const path = require('path');
const PugPlugin = require('pug-plugin');
module.exports = {
output: {
path: path.join(__dirname, 'public/'),
publicPath: '/', // must be defined any path, `auto` is not supported
},
entry: {
index: './src/pages/index.pug', // ==> public/index.html
},
plugins: [
new PugPlugin(), // add the plugin
],
module: {
rules: [
{
test: /\.pug$/,
loader: PugPlugin.loader, // add the pug-loader
},
],
},
};
Note: this configuration work without
html-webpack-plugin
.
To save extracted HTML
, you had to add new HtmlWebpackPlugin ({...})
to webpack.plugins
for each file:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
'main': 'main.js',
'styles': 'styles.scss',
},
plugins: [
new HtmlWebpackPlugin({
template: 'page01.pug',
filename: 'page01.html',
}),
// ...
new HtmlWebpackPlugin({
template: 'page66.pug',
filename: 'page66.html',
}),
]
}
Each time will be created new instance of the plugin, initialized and processed. This is not good for huge amount of files.
This plugin can extract and save HTML
directly from webpack entry
. It is very practical to define all static resources (js, sass, pug, html) together in one place:
const PugPlugin = require('pug-plugin');
module.exports = {
entry: {
'main': 'main.js',
'styles': 'styles.scss',
'index': 'index.html', // now is possible define HTML file in entry
'page01': 'page01.pug', // now is possible define PUG file in entry
// ...
'page77': 'page77.pug',
},
plugins: [
new PugPlugin(), // supports zero config using default webpack output options
]
};
pug
files from webpack entry
and save extracted HTML into separate file
module.exports = {
entry: {
about: 'src/templates/about.pug', // extract HTML and save to output directory as `about.html`
},
}
html
files from webpack entry
and save it without additional plugins like html-webpack-plugin
module.exports = {
entry: {
index: 'src/templates/index.html', // save the HTML to output directory as `index.html`
},
}
scss
css
files from webpack entry
without additional plugins like mini-css-extract-plugin
module.exports = {
entry: {
styles: 'src/assets/scss/main.scss', // extract CSS and save to output directory as `styles.css`
},
}
webpack entry
syntax to define source / output files separately for each entry
module.exports = {
entry: {
about: { import: 'src/pages/about/template.pug', filename: 'public/[name].html' },
examples: { import: 'vendor/examples/index.html', filename: 'public/some/path/[name].html' },
},
};
webpack entry
API for the plugin option filename
, its can be as a template string
or a function
const PugPluginOptions = {
filename: (pathData, assetInfo) => {
return pathData.chunk.name === 'main' ? 'assets/css/styles.css' : '[path][name].css';
}
}
const PugPluginOptions = {
modules: [
{
test: /\.(pug)$/,
sourcePath: path.join(__dirname, 'src/templates/'),
outputPath: path.join(__dirname, 'public/'),
filename: '[name].html'
},
{
test: /\.(html)$/,
sourcePath: path.join(__dirname, 'src/vendor/static/'),
outputPath: path.join(__dirname, 'public/some/other/path/'),
},
{
test: /\.(sass|scss)$/,
sourcePath: path.join(__dirname, 'src/assets/sass/'),
outputPath: path.join(__dirname, 'public/assets/css/'),
filename: isProduction ? '[name].[contenthash:8].css' : '[name].css'
},
],
};
post process
for modules to handle the extracted content before emit
const PugPluginOptions = {
modules: [
{
test: /\.pug$/,
postprocess: (content, entry, compilation) => {
// TODO: your can here handle extracted HTML
return content;
},
},
],
};
pug
files
const PugPlugin = require('pug-plugin');
module.exports = {
module: {
rules: [
{
test: /\.pug$/,
loader: PugPlugin.loader,
},
],
},
};
See the description of the
pug-loader
options here.
webpack entry
in separate file without generating unexpected empty js files,const PugPlugin = require('pug-plugin');
module.exports = {
entry: {
'styles': 'styles.scss',
},
plugins: [
new PugPlugin({
modules: [
PugPlugin.extractCss(),
],
}),
],
module: {
rules: [
{
test: /\.(css|sass|scss)$/,
use: [ 'css-loader', 'sass-loader' ],
}
],
},
};
Neither
MiniCssExtractPlugin
norMiniCssExtractPlugin.loader
is required when usingPugPlugin.extractCss
.
ThePugPlugin.extractCss(options)
has the same options as the plugin options.
require()
directly in pug and replace the source filename with a public hashed name. In this case is no need to define the style in the webpack entry:
link(rel='stylesheet' href=require('~Styles/main.scss'))
output
<link rel="stylesheet" href="/assets/css/main.6f4d012e.css">
see complete example of usageThe plugin options are default options for self plugin and all plugin modules
.
In a defined module
any option can be overridden.
enabled
Type: boolean
Default: true
Enable/disable the plugin.
test
Type: RegExp
Default: /\.pug$/
The search for a match of entry files.
sourcePath
Type: string
Default: webpack.options.context
The absolute path to sources.
outputPath
Type: string
Default: webpack.options.output.path
The output directory for processed entries.
filename
Type: string | Function
Default: webpack.output.filename || '[name].html'
The file name of output file.
string
then following substitutions are available in template strings
[name]
Only filename without extension or path.[base]
Filename with extension.[path]
Only path, without filename.[path][name]
The path and filename without extension.[ext]
Extension with leading .
.[id]
The ID of the chunk.[contenthash]
The hash of the content.[contenthash:nn]
The nn
is the length of hashes (defaults to 20).Function
then following parameters are available in the function:
@param {webpack PathData} pathData
See the description of this type here@param {webpack AssetInfo} assetInfo
@return {string}
The name or template string of output file.postprocess
Type: Function
Default: null
The post process for extracted content from compiled entry.
The following parameters are available in the function:
@param {string | []} content
The content of compiled entry. Can be a string (for html entry), an array (for css).@param {EntryAssetInfo} info
The info of current asset.@param {webpack Compilation} compilation
The webpack compilation object.@return {string | null}
Return a string content to save it to output directory.null
then the compiled content of the entry will be ignored, and will be saved original content compiled as JS module.
Returning null
can be useful for debugging to see the source of the compilation of the webpack loader./**
* @typedef {Object} EntryAssetInfo
* @property {boolean} [verbose = false] Whether information should be displayed.
* @property {boolean} isEntry True if is the asset from entry, false if asset is required from pug.
* @property {string} entryFile The entry file.
* @property {string | (function(PathData, AssetInfo): string)} filename The filename template or function.
* @property {string} sourceFile The source file.
* @property {string} assetFile The output asset file with relative path by webpack output path.
*/
modules
Type: PluginOptions[]
Default: []
The array of objects of type PluginOptions
to separately handles of files of different types.
The description of @property
of the type PluginOptions
see above, by Plugin options.
/**
* @typedef {Object} PluginOptions
* @property {boolean} enabled
* @property {boolean} verbose
* @property {RegExp} test
* @property {string} sourcePath
* @property {string} outputPath
* @property {string | function(PathData, AssetInfo): string} filename
* @property {function(string, EntryAssetInfo, Compilation): string | null} postprocess
*/
verbose
Type: boolean
Default: false
Show the file information at processing of entry.
webpack.config.js
const path = require('path');
const PugPlugin = require('pug-plugin');
module.exports = {
output: {
path: path.join(__dirname, 'public/'), // output path
publicPath: '/', // must be defined a real publicPath, `auto` not supported!
},
entry: {
'index': 'templates/index.pug', // save HTML into '<__dirname>/public/index.html'
},
plugins: [
new PugPlugin(),
],
module: {
rules: [
{
test: /\.pug$/,
loader: PugPlugin.loader,
// this loader options are optional, but recommended for faster compilation
options: {
method: 'render'
},
},
],
},
};
Dependency: html-loader
This loader is need to handle the .html
file type.
Install: npm install html-loader --save-dev
webpack.config.js
const path = require('path');
const PugPlugin = require('pug-plugin');
module.exports = {
output: {
path: path.join(__dirname, 'public/'), // output path
publicPath: '/', // must be defined a real publicPath, `auto` not supported!
},
entry: {
'example': 'vendor/pages/example.html', // save HTML into '<__dirname>/public/example.html'
},
plugins: [
new PugPlugin({
modules: [
// add the module object to match `.html` files in webpack entry
{ test: /\.html$/, filename: '[name].html' }
],
}),
],
module: {
rules: [
// add the loader to handle `.html` files
{
test: /\.html$/,
loader: 'html-loader',
options: {
// disable processing of resources in static HTML, leave as is
sources: false,
// webpack use CommonJS module
esModule: false,
},
},
],
},
};
require
in pugDependencies:
css-loader
handles .css
files and prepare CSS for any CSS extractorsass-loader
handles .scss
filessass
compiles Sass to CSSInstall: npm install css-loader sass sass-loader --save-dev
In this case no need to define the style in webpack entry.
The CSS is extracted from a style using the require()
function directly in the pug template.
The pug template src/templates/index.pug
:
html
head
link(href=require('~Styles/my-style.scss'))
body
p Hello World!
The generated HTML:
<html>
<head>
<link rel="stylesheet" href="/assets/css/my-style.f57966f4.css">
</head>
<body>
<p>Hello World!</p>
</body>
</html>
The extracted CSS is saved in the file assets/css/my-style.f57966f4.css
in the output directory.
Add to the webpack.config.js
following:
const PugPlugin = require('pug-plugin');
module.exports = {
entry: {
// ...
index: 'src/templates/index.pug', // the pug file with required style
},
plugins: [
// ...
// handle pug and style files defined in webpack.entry
new PugPlugin({
modules: [
PugPlugin.extractCss(), // the module to extract CSS
],
}),
],
module: {
rules: [
// ...
{
test: /\.pug$/,
loader: PugPlugin.loader, // the pug-loader is already included in the PugPlugin
},
{
test: /\.(css|sass|scss)$/,
type: 'asset/resource', // extracts css via require in pug
generator: {
filename: 'assets/css/[name].[hash:8].css', // save extracted css in output directory
},
use: [ 'css-loader', 'sass-loader' ], // extract css from a style
}
],
},
}
Note: don't needed any additional plugin, like
mini-css-extract-plugin
.
⚠️ Don't do that in JavaScript:
import ./src/styles.scss
. This is popular but dirty way.
The importing of styles in JavaScript triggers the events in Webpack which call the mini-css-extract-plugin
loader
to extract CSS from imported style source. Then the html-webpack-plugin
using a magic add the <link href="styles.css">
with filename of extracted CSS to any HTML file in head at last position.
Your can't define in which HTML file will be added style and in which order. You are not in control of this process!
This process requires two different plugins and has poor performance.
The single pug-plugin
does it with right way, in one step and much faster.
Correct ways
- add a source style file directly in pug via
require
, likelink(href=require('~Styles/styles.scss'))
- add a compiled css file directly in pug, like
link(href='/assets/styles.css')
and add the source of the style in webpack entry.
Yes, in this case may be needed additional assets manifest plugin to replace original filename with hashed name. But this is the right way.
In the future, will be added support to automatically replace the asset file name with a hashed one.
Dependencies:
css-loader
handles .css
files and prepare CSS for any CSS extractorsass-loader
handles .scss
filessass
compiles Sass to CSSInstall: npm install css-loader sass sass-loader --save-dev
webpack.config.js
const path = require('path');
const PugPlugin = require('pug-plugin');
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
output: {
path: path.join(__dirname, 'public/'), // output path
publicPath: '/', // must be defined a real publicPath, `auto` not supported!
},
entry: {
'css/styles': 'src/assets/main.scss', // save CSS into '<__dirname>/public/css/styles.css'
},
plugins: [
new PugPlugin({
modules: [
// add the module to extract CSS
// see options https://github.com/webdiscus/pug-plugin#options
PugPlugin.extractCss({
filename: isProduction ? '[name].[contenthash:8].css' : '[name].css',
})
],
}),
],
module: {
rules: [
{
test: /\.(css|sass|scss)$/,
use: [
{
loader: 'css-loader',
options: {}, // see options https://github.com/webpack-contrib/css-loader#options
},
{
loader: 'sass-loader',
options: {}, // see options https://github.com/webpack-contrib/sass-loader#options
},
],
},
],
},
};
When using
PugPlugin.extractCss()
to extract CSS fromwebpack entry
the following plugins are not needed:
- mini-css-extract-plugin
- webpack-remove-empty-scripts - fix plugin for mini-css-extract-plugin
The plugin module
PugPlugin.extractCss
extract and save pure CSS, without eny empty JS files.⚠️ When using
PugPlugin.extractCss()
don't use thestyle-loader
.
webpack entry
webpack.config.js
const path = require('path');
const PugPlugin = require('pug-plugin');
const isProduction = process.env.NODE_ENV === 'production';
const PATH_COMPONENTS = path.join(__dirname, 'src/components/');
module.exports = {
output: {
path: path.join(__dirname, 'public/'), // output path
publicPath: '/', // must be defined a real publicPath, `auto` not supported!
},
entry: {
// use source / output paths, defined in module options
'assets/js/main': './src/assets/main.js', // output '<__dirname>/public/assets/js/main.js'
'styles': 'src/assets/main.scss', // output '<__dirname>/public/assets/css/styles.css'
'about': 'about.pug', // output '<__dirname>/public/pages/about.html'
'examples': 'examples.html', // output '<__dirname>/public/static/examples.html'
// use absolute path if a source file is not in the defined `sourcePath`
// use custom output filename individual for the entry
'js/demo': {
import: path.join(PATH_COMPONENTS, 'demo/main.js'),
filename: 'assets/[name]-[contenthash:8].js',
// output '<__dirname>/public/assets/js/demo-abcd1234.js'
},
'css/demo': {
import: path.join(PATH_COMPONENTS, 'demo/main.scss'),
filename: 'assets/[name]-[contenthash:8].css',
// output '<__dirname>/public/assets/css/demo-abcd1234.css'
},
'demo': {
import: path.join(PATH_COMPONENTS, 'demo/main.pug'),
filename: 'pages/[name].html',
// output '<__dirname>/public/pages/demo.html'
},
},
plugins: [
new PugPlugin({
enabled: true,
verbose: false,
modules: [
// add the module object to define custom options for `.pug`
{
test: /\.pug$/,
filename: '[name].html',
sourcePath: 'src/templates/pug/', // define custom path to sources, relative by webpack.config.js
outputPath: 'pages/', // define custom output path, relative by webpack output.path
},
// add the module object to match `.html` files in webpack entry
{
test: /\.html$/,
filename: '[name].html',
sourcePath: 'src/templates/html/',
outputPath: 'static/',
},
// add the module to extract CSS
PugPlugin.extractCss({
filename: isProduction ? '[name].[contenthash:8].css' : '[name].css',
sourcePath: 'src/assets/sass/',
outputPath: 'assets/css/',
})
],
}),
],
module: {
rules: [
// pug
{
test: /\.pug$/,
loader: PugPlugin.loader,
options: {
method: 'render'
},
},
// html
{
test: /\.html$/,
loader: 'html-loader',
options: {
// disable processing of resources in static HTML, leave as is
sources: false,
// webpack use CommonJS module
esModule: false,
},
},
// styles
{
test: /\.(css|sass|scss)$/,
use: [
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
},
],
},
],
},
};
pug-plugin
and pug-loader
with html
render method.Don't use it if you don't know why you need it.
It's only the example of the solution for possible trouble by usage thehtml-loader
.
Usually is used therender
orcompile
method inpug-loader
options.
For example, by usage in pug both static and dynamic resources.
index.pug
html
head
//- Static resource URL from public web path should not be parsed, leave as is.
link(rel='stylesheet' href='/absolute/assets/about.css')
//- Required resource must be processed.
Output to /assets/css/styles.8c1234fc.css
link(rel='stylesheet' href=require('./styles.scss'))
body
h1 Hello World!
//- Static resource URL from public web path should not be parsed, leave as is.
img(src='relative/assets/logo.jpeg')
//- Required resource must be processed.
Output to /assets/images/image.f472de4f4.jpg
img(src=require('./image.jpeg'))
webpack.config.js
const fs = require('fs');
const path = require('path');
const PugPlugin = require('pug-plugin');
module.exports = {
mode: 'production',
output: {
path: path.join(__dirname, 'public/'),
publicPath: '/',
},
entry: {
index: './src/index.pug',
},
plugins: [
new PugPlugin({
modules: [PugPlugin.extractCss()],
}),
],
module: {
rules: [
{
test: /\.pug$/,
use: [
{
loader: 'html-loader',
options: {
// Webpack use CommonJS module
esModule: false,
sources: {
// MEGA IMPORTANT!
urlFilter: (attribute, value) => path.isAbsolute(value) && fs.existsSync(value),
},
},
},
{
loader: PugPlugin.loader,
options: {
method: 'html', // usually is used the `render` method
},
},
],
},
{
test: /\.(css|sass|scss)$/,
type: 'asset/resource', // process required scss/css in pug
generator: {
filename: 'assets/css/[name].[contenthash:8].css',
},
use: ['css-loader', 'sass-loader'],
},
{
test: /\.(png|jpg|jpeg)/,
type: 'asset/resource', // process required images in pug
generator: {
filename: 'assets/images/[name].[hash:8][ext]',
},
},
],
},
};
⚠️ When used
PugPlugin
andhtml-loader
A static resource URL from a public web path should not be parsed by the
html-loader
. Leave the URL as is:img(src='/assets/image.jpg') link(rel='stylesheet' href='assets/styles.css')
Loading a resource with
require()
should be handled via webpack:img(src=require('./image.jpg')) link(rel='stylesheet' href=require('./styles.css'))
For this case add to
html-loader
the option:
sources: { urlFilter: (attribute, value) => path.isAbsolute(value) && fs.existsSync(value) }
npm run test
will run the unit and integration tests.
npm run test:coverage
will run the tests with coverage.
ansis
- colorize text in terminalpug-loader
pug GitHub
pug API Reference
1.2.4 (2022-01-28)
FAQs
Pug plugin for webpack handles a template as an entry point, extracts CSS and JS from their sources referenced in Pug.
The npm package pug-plugin receives a total of 3,187 weekly downloads. As such, pug-plugin popularity was classified as popular.
We found that pug-plugin demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Snyk's use of malicious npm packages for research raises ethical concerns, highlighting risks in public deployment, data exfiltration, and unauthorized testing.
Research
Security News
Socket researchers found several malicious npm packages typosquatting Chalk and Chokidar, targeting Node.js developers with kill switches and data theft.
Security News
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.