webpack 如何处理链接时使用树枝加载器

cgfeq70w  于 5个月前  发布在  Webpack
关注(0)|答案(1)|浏览(62)

我使用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>

kmynzznz

kmynzznz1#

要处理twig模板中的脚本,样式,图像等源文件,您可以使用HTML Bundler Plugin for Webpack。该插件解析任何模板(包括Twig)中的资源引用。
例如,有一个简单的twig模板:

<!DOCTYPE html>
<html>
  <head>
    {# source style file relative to template #}
    <link href="./styles.scss" rel="stylesheet" />
    {# source script file relative to template #}
    <script src="./script.ts" defer="defer"></script>
  </head>
  <body>
    <h1>Hello Twig!</h1>
    {% block content %}{% endblock %}
    
    {# source image file using Webpack alias `@images`, or a relative path #}
    <img src="@images/test.jpg" />

    {# you can inline SVG content directly in HML #}
    <img src="@images/sprite.svg?inline" />

    {# to extends or include, you can use the namespaces, or a relative path #}
    {% include "@partials/footer.twig" %}
  </body>
</html>

字符串
简单的Webpack配置:

const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');

module.exports = {
  resolve: {
    alias: {
      // aliases used in template
      '@images': path.join(__dirname, 'src/shared/assets/images/'),
    },
  },
  plugins: [
    new HtmlBundlerPlugin({
      // define a relative or absolute path to entry templates
      entry: 'src/views/',
      // - OR - define many templates manually
      entry: {
        // the key is an output file path w/o .html
        index: 'src/views/page/home.twig' // => dist/index.html
      },
      preprocessor: 'twig', // use TwigJS templating engine
      preprocessorOptions: {
        // aliases used for extends/include
        namespaces: {
          partials: 'src/views/partials/',
        },
      },
      data: {
        // you can define here global variables used in all templates
      },
      js: {
        // output filename of compiled JavaScript, used if `inline` option is false (defaults)
        filename: 'assets/js/[name].[contenthash:8].js',
        //inline: true, // inlines JS into HTML
      },
      css: {
        // output filename of extracted CSS, used if `inline` option is false (defaults)
        filename: 'assets/css/[name].[contenthash:8].css',
        //inline: true, // inlines CSS into HTML
      },
    }),
  ],
  module: {
    rules: [
      {
        test: /\.(ts)$/,
        use: 'ts-loader',
      },
      {
        test: /\.(css|sass|scss)$/,
        use: ['css-loader', 'sass-loader'],
      },
      {
        test: /\.(ico|png|jp?g|webp|svg)$/,
        type: 'asset/resource',
        generator: {
          filename: 'assets/img/[name].[contenthash:8][ext]'
        }
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'assets/fonts/[name][ext]'
        }
      },
    ],
  },
};


该插件可以自动检测路径中的模板。
对于高级用例,例如,如果你想根据目录名动态生成一个自定义的输出html文件名,你可以使用文件名输入选项作为一个函数。
生成的HTML看起来像:

<html>
  <head>
    <link href="assets/css/styles.05e4dd86.css" rel="stylesheet" />
    <script src="assets/js/script.f4b855d8.js" defer="defer"></script>
  </head>
  <body>
    <h1>Hello Twig!</h1>
    <img src="assets/img/test.58b43bd8.jpg" />
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960" width="48" height="48">
      ...SVG sprite...
    </svg>
    <div>My included footer</div>
  </body>
</html>


该插件可以将SVG作为内容和图像作为base64内联到HTML中。
该插件解析模板中的源文件(如html-loader),提取CSS(如mini-css-extract-plugin),JS,渲染Twig模板。因此,不需要额外的插件和加载器,如html-webpack-plugin,mini-css-extract-plugin,html-loader,twig-loader。

相关问题