我使用twig loader来处理twig文件,但是它不像html loader那样处理资源链接。
有一个类似的question,但提出的解决方案并没有帮助我。当使用html-loader extract-loader和twig-loader一起,出现了一个问题,因为可能有嵌套内模板,html-loader收到一个尚未处理的twig文件
Webpack配置:
type WebpackBuildMode = "development" | "production";
interface WebpackBuildPaths {
readonly pages: string;
readonly src: string;
readonly output: string;
readonly svgo: string;
readonly public: string;
}
interface WebpackBuildEnv {
readonly mode: WebpackBuildMode,
readonly port: number;
readonly svg: boolean;
}
interface WebpackBuildOptions {
readonly mode: WebpackBuildMode;
readonly paths: WebpackBuildPaths;
readonly isDev: boolean;
readonly port: number;
readonly isSvg: boolean;
}
const buildEntry = (options: WebpackBuildOptions): webpack.Configuration["entry"] => {
const {paths} = options;
const entry = fs.readdirSync(paths.pages).reduce((acc, dir) => {
acc.push([
dir,
path.resolve(paths.pages, dir, 'script.ts')
]);
return acc;
}, []);
return Object.fromEntries(entry);
};
const buildPlugins = (options: WebpackBuildOptions): webpack.Configuration["plugins"] => {
const {paths, isSvg, isDev} = options;
const html = fs.readdirSync(paths.pages).filter(dir => dir !== 'base').map(dir => (
new HtmlWebpackPlugin({
inject: false,
template: path.resolve(paths.pages, dir, "template.twig"),
templateParameters: (compilation, assets, assetTags, options) => {
const compilationAssets = compilation.getAssets();
const sprites = compilationAssets.filter((asset: any) => asset.name.includes('sprites/'));
return {
compilation,
webpackConfig: compilation.options,
htmlWebpackPlugin: {
tags: assetTags,
files: {
...assets,
svgSprites: sprites.map((sprite: any) => ([
sprite.name.split('/')[1],
sprite.source._valueAsString
])),
},
options,
},
};
},
filename: `${dir}.html`,
minify: false,
})
));
const plugins: webpack.Configuration["plugins"] = [
new MiniCssExtractPlugin({
filename: "[name].css",
}),
new SvgChunkWebpackPlugin({
filename: "sprites/[name].svg",
generateSpritesPreview: true,
svgstoreConfig: {
svgAttrs: {
'aria-hidden': true,
style: 'position: absolute; width: 0; height: 0; overflow: hidden;'
}
}
}),
];
if (!isSvg) {
plugins.push(...html);
}
return plugins;
};
const buildLoaders = (options: WebpackBuildOptions): webpack.Configuration["module"]["rules"] => {
const {isDev, isSvg, paths} = options;
const tsLoader = {
test: /\.ts$/,
use: "ts-loader",
exclude: /node_modules/,
};
const twigLoader: webpack.RuleSetRule = {
test: /\.twig$/,
use: [
{
loader: "html-loader",
options: {
sources: {
list: [
{
tag: "img",
attribute: "src",
type: "src",
},
],
},
minimize: false,
},
},
{
loader: "twig-loader",
},
],
};
const styleLoader = {
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader",
],
};
const fontLoader = {
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: "asset/resource",
generator: {
filename: 'assets/fonts/[name][ext]'
}
};
const svgLoader = {
test: /\.svg$/i,
include: [
path.resolve(paths.src, "shared", "assets", "icons")
],
use: [
{
loader: (SvgChunkWebpackPlugin as any).loader,
options: {
configFile: paths.svgo,
},
},
],
};
const assetsLoader = {
test: /\.(png|jpg|jpeg|gif|svg)$/i,
type: "asset/resource",
exclude: [
path.resolve(paths.src, "shared", "assets", "icons")
],
generator: {
outputPath: (pathData: any) => {
const dirname = path.dirname(pathData.filename);
let subDir = '';
if (dirname.includes('src/shared/assets/images')) {
subDir = "assets/images/" + dirname.replace('src/shared/assets/images', '') + "/";
}
return subDir;
},
publicPath: (pathData: any) => {
const dirname = path.dirname(pathData.filename);
let subDir = '';
if (dirname.includes('src/shared/assets/images')) {
subDir = "assets/images/" + dirname.replace(/src\/shared\/assets\/images(\/?)/ig, '') + "/";
}
return subDir;
},
filename: '[name][ext]'
}
};
const loaders: webpack.Configuration["module"]["rules"] = [
styleLoader,
fontLoader,
assetsLoader,
svgLoader,
tsLoader,
];
if (!isSvg) {
loaders.push(twigLoader);
}
return loaders;
};
const buildDevServer = (options: WebpackBuildOptions): DevServerConfiguration => {
const {port} = options;
return {
port,
open: true,
hot: 'only',
}
}
const buildResolve = (options: WebpackBuildOptions): webpack.Configuration["resolve"] => {
const {paths} = options;
return {
extensions: [".ts", ".js"]
};
};
const buildWebpackConfig = (options: WebpackBuildOptions): webpack.Configuration => {
const {mode, isDev, isSvg, paths} = options;
return {
mode: mode,
entry: buildEntry(options),
plugins: buildPlugins(options),
module: {
rules: buildLoaders(options),
},
devtool: isDev ? 'inline-source-map' : undefined,
devServer: isDev && !isSvg ? buildDevServer(options) : undefined,
resolve: buildResolve(options),
output: {
path: paths.output,
filename: '[name].js',
clean: true,
},
optimization: {
runtimeChunk: "single",
}
};
};
const config = (env: WebpackBuildEnv): webpack.Configuration => {
const mode = env.mode;
const isSvg = env.svg;
const port = env.port || 3000;
const isDev = mode === "development";
const paths: WebpackBuildPaths = {
src: path.resolve(__dirname, "src"),
output: path.resolve(__dirname, "build"),
pages: path.resolve(__dirname, "src", "pages"),
svgo: path.resolve(__dirname, "config", "svgo", "svgo.config.ts"),
public: path.resolve(__dirname, "public"),
};
return buildWebpackConfig({
isDev,
isSvg,
mode,
port,
paths,
});
};
export default config;
字符串
Package.json:
{
"name": "krep-comp",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"build": "webpack --env mode=production --env svg && webpack --env mode=production"
},
"keywords": [],
"license": "ISC",
"devDependencies": {
"@types/node": "^20.8.4",
"@types/webpack": "^5.28.3",
"css-loader": "^6.8.1",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.3",
"mini-css-extract-plugin": "^2.7.6",
"sass": "^1.69.2",
"sass-loader": "^13.3.2",
"style-loader": "^3.3.3",
"svg-chunk-webpack-plugin": "^4.0.2",
"ts-loader": "^9.5.0",
"ts-node": "^10.9.1",
"typescript": "^5.2.2",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"twig-loader": "^0.5.5"
}
}
型
项目结构:
├── webpack.config.ts
├── package.json
├── src
| ├── app
| | ├── main
| | | ├── template.twig
| | | ├── script.ts
| ├── shared
| | ├── assets
| | | ├── images
| | | | ├── test.jpg
型
[*] Twig tempalte**
<!doctype html>
<html lang="{% block lang %}ru{% endblock %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{% block title %}{% endblock %}</title>
{% for js in htmlWebpackPlugin.files.js %}
{% if js|split('.')[0] == htmlWebpackPlugin.options.filename|split('.')[0] %}
<script defer="defer" src="{{ js }}"></script>
{% endif %}
{% endfor %}
{% for css in htmlWebpackPlugin.files.css %}
{% if css|split('.')[0] == htmlWebpackPlugin.options.filename|split('.')[0] %}
<link href="{{ css }}" rel="stylesheet">
{% endif %}
{% endfor %}
</head>
<body>
{% block content %}{% endblock %}
<img src="/src/shared/assets/images/test.jpg">
{% for sprite in htmlWebpackPlugin.files.svgSprites %}
{% if sprite[0]|split('.')[0] == htmlWebpackPlugin.options.filename|split('.')[0] %}
{{ sprite[1] }}
{% endif %}
{% endfor %}
</body>
</html>
型
1条答案
按热度按时间kmynzznz1#
要处理
twig
模板中的脚本,样式,图像等源文件,您可以使用HTML Bundler Plugin for Webpack。该插件解析任何模板(包括Twig)中的资源引用。例如,有一个简单的twig模板:
字符串
简单的Webpack配置:
型
该插件可以自动检测路径中的模板。
对于高级用例,例如,如果你想根据目录名动态生成一个自定义的输出html文件名,你可以使用文件名输入选项作为一个函数。
生成的HTML看起来像:
型
该插件可以将SVG作为内容和图像作为base64内联到HTML中。
该插件解析模板中的源文件(如html-loader),提取CSS(如mini-css-extract-plugin),JS,渲染Twig模板。因此,不需要额外的插件和加载器,如html-webpack-plugin,mini-css-extract-plugin,html-loader,twig-loader。