3

I'm trying to modify my webpack configuration such that font files (.woff, .woff2 .eot, .ttf) coming from peer dependencies won't be included in the build. Here's my Webpack config:

webpack.common.js

module.exports = {
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js',
    publicPath: '/',
    libraryTarget: 'umd',
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html"),
    }),
    new CopyPlugin({
      patterns: [
        { from: path.resolve(__dirname, 'src/assets'), to: path.resolve(__dirname, 'dist/assets') }
      ],
    }),
  ],
  externals: {
    lodash: 'lodash',
    '@scoped/scoped-ui': '@scoped/scoped-ui',
    '@scoped/scoped-web-common': '@scoped/scoped-web-common',
    '@scoped/scoped-rich-text-editor': '@scoped/scoped-rich-text-editor',
    '@ckeditor/ckeditor5-react': '@ckeditor/ckeditor5-react',
    bootstrap: 'bootstrap',
    'react-bootstrap': 'react-bootstrap'
  },
  // also tried the array format
  // externals: ['lodash', '@scoped/scoped-ui', '@scoped/scoped-web-common', '@ckeditor/ckeditor5-react', 'bootstrap', 'react-bootstrap'], 
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: [
          'babel-loader',
          {
            loader: 'eslint-loader',
            options: {
              emitWarning: true,
              formatter: 'table',
            },
          },
        ],
      },
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name].[ext]',
              outputPath: 'static/img',
            },
          },
        ],
      },
      {
        test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name].[ext]',
              outputPath: '/assets/'
            },
          },
        ],
      },
      {
        test: /\.ttf$/,
        use: [
          {
            loader: 'ttf-loader',
            options: {
              name: './font/[hash].[ext]',
              outputPath: '/assets/'
            },
          },
        ],
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader',
        ],
      },
    ],
  },
};

Webpack (& related) version:

"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^5.1.3"

Peer dependencies in package.json:

 "peerDependencies": {
    "@ckeditor/ckeditor5-react": "^2.1.0",
    "@scoped/scoped-rich-text-editor": "0.0.1-56",
    "@scoped/scoped-ui": "^1",
    "@scoped/scoped-web-common": "^1",
    "bootstrap": "^4.4.1",
    "react-bootstrap": "^1.0.0-beta.17",
    "lodash": "4.17.21"
 },

The result of my build script is the following file structure:

    .
    ┠ dist
       ┠ assets
           ┠ [hashed].ttf
           ┠ [hashed].eot
           ┠ [hashed].woff
           ┠ [hashed].woff2
           ┠ [someImage].jpg
           ┠ [someOtherImage].png
       ┠ static
       ┠ index_bundle.js
       ┠ index_bundle.js.map
       ┠ index.html

Things I've tried:

  1. Add an externals key to the webpack config as suggested in the following question: Webpack to build without including peer dependencies

  2. Try to exclude node_modules from the rules for the font files.

  3. Remove the rules for the font files themselves.

Both 2) & 3) give me the same result, i.e build script fails with errors that look like the following:

ERROR in ./node_modules/@scoped/scoped-ui/build/assets/fonts/418e7417-47f3-40a1-8817-519a566f9d82.eot 1:1
Module parse failed: Unexpected character '@' (1:1)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)
 @ ./node_modules/@scoped/scoped-ui/build/styles/font.css (./node_modules/css-loader/dist/cjs.js!./node_modules/@scoped/scoped-ui/build/styles/font.css) 4:0-101 33:73-102
 @ ./node_modules/css-loader/dist/cjs.js!./node_modules/@scoped/scoped-ui/build/styles/main.css
 @ ./node_modules/@scoped/scoped-ui/build/styles/main.css
 @ ./src/index.js
 @ ./index.js

ERROR in ./node_modules/@scoped/scoped-ui/build/assets/fonts/4cc8f5da-4e24-4929-8efb-866ffcb1fe7e.eot 1:0
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)
 @ ./node_modules/@scoped/scoped-ui/build/styles/font.css (./node_modules/css-loader/dist/cjs.js!./node_modules/@scoped/scoped-ui/build/styles/font.css) 28:0-102 57:74-104
 @ ./node_modules/css-loader/dist/cjs.js!./node_modules/@scoped/scoped-ui/build/styles/main.css
 @ ./node_modules/@scoped/scoped-ui/build/styles/main.css
 @ ./src/index.js
 @ ./index.js

lycan1385
  • 127
  • 1
  • 10

1 Answers1

0

I think this can be solved by using a custom resolve plugin:

webpack.config.js

const path = require('path');

const PreventFontsFromPeerDeps = {
  apply (resolver) {
    const beforeFinalFile = resolver.getHook('before-final-file');
    
    let peerDepsFromMainRepo;
    beforeFinalFile.tap("PreventFontsFromPeerDeps", (request, resolveContext) => {
      if (!peerDepsFromMainRepo) {
        peerDepsFromMainRepo = request.descriptionFileData.peerDependencies;

        return;
      }

      const { path } = request;
      
      const isPathAFont = /* ... */;
      const isFromPeerDeps = Object.keys(peerDepsFromMainRepo).some(k => path.includes(`node_modules/${k}`));
      if (isFromPeerDeps && isPathAFont) {
        // By returning this, this request will be ignored.
        return { path: false };
      }
    });
  },
};

/**
 * @type {import("webpack/types").Configuration}
 */
const config = {
  /* ... */

  resolve: {
    plugins: [PreventFontsFromPeerDeps]
  },

  /* ... */
};

module.exports = config;

The part of the bundling process where this custom plugin intervenes can be thought of as the resolving process.
Here is where resources are resolved. This resolving process consists of multiple stages, such as: determining the package.json(also called description file) of the required module, determine whether the path refers to a path or to a directory etc.
These stages are internally represented as hooks in webpack's parlance. One of those hooks is final-file, which is responsible for determining whether the file exists or not. By using before-final-file, we're adding some sort of priority, meaning that our custom logic will run first.

The peerDepsFromMainRepo refers to the very first file that will be resolved. This file will most likely be part of the repo, so the package.json associated with it will be the one of the root directory, which means that we will be able to get the peerDependencies object from there.
The request object holds all the information we need, as we can see from this image:

enter image description here

What the plugin essentially does is to grab the peer dependencies from the package.json file of the project's root directory and then, for each incoming request(request = path to a resource), it checks whether that resource is part of the peer dependencies or not.

Note: in the above image I took a simpler example, where I added lodash as a peer dependency. You can find the example here.


I talked in a bit more detail about the resolving process in this SO answer.

Andrei Gătej
  • 11,116
  • 1
  • 14
  • 31