使用twig-loader时如何处理资源链接?
问题:Twig-loader无法处理资源链接,嵌套模板导致html-loader异常
我使用twig-loader处理Twig模板文件,但它无法像html-loader那样处理资源链接。Stack Overflow上有类似问题,但现有解决方案对我无效。当同时使用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模板
<!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>
内容的提问来源于stack exchange,提问作者kxhcfk
相关产品推荐
相关产品推荐

