sveltejs / svelte-loader

Webpack loader for svelte components.
MIT License
594 stars 73 forks source link

css failure on webpack serve on any component update when App.svelte includes tailwind #200

Closed flipkickmedia closed 2 years ago

flipkickmedia commented 2 years ago

Win11+Webpack v5.68+svelte 3.64.3+tailwind 3.0.19+postcss 8.4.6+pnpm+node v16.13.2

A change to a component file located inside node_modules isn't being loaded correctly causing the css build to fail after the first run during webpack serve.

The error is happening on a change to a file located inside node_modules during development - the logs should provide enough info on the structure. Im editing the file directly, Ive tried with npm.

# svelte-loader 3.64 - node_modules/svelte-loader/index.js line 19+

    if (options.cssPath) {
        const css = virtualModules.get(options.cssPath);
        virtualModules.delete(options.cssPath);

the const css === undefined :(

The first run css has the correct css in it, on the second run it hasn't

The path to the css: "C:/Users/tbmst/Documents/svelte-template-webpack-

typescript/node_modules/.pnpm/git.flipkick.media+entitywind.io+svelte-component-panelbar@6370e35f6a4c236e1d58330e3d7ea3f6338a85ba/node_modules/svelte-component-panelbar/src/components/PanelBar.svelte.6.css"

The path looks wrong to me but I assume that's a virtual path. Is there any way to get this content actually written to disk?

Compiled with problems:

ERROR in ./src/App.svelte (./src/App.svelte.webpack[javascript/auto]!=!./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js!./node_modules/svelte-loader/index.js?cssPath=C:/Users/tbmst/Documents/svelte-template-webpack-typescript/src/App.svelte.0.css!./src/App.svelte)

Module build failed (from ./node_modules/postcss-loader/dist/cjs.js):
Error: PostCSS received undefined instead of CSS string
    at new Input (C:\Users\tbmst\Documents\svelte-template-webpack-typescript\node_modules\postcss\lib\input.js:24:13)
    at parse (C:\Users\tbmst\Documents\svelte-template-webpack-typescript\node_modules\postcss\lib\parse.js:8:15)
    at new LazyResult (C:\Users\tbmst\Documents\svelte-template-webpack-typescript\node_modules\postcss\lib\lazy-result.js:133:16)
    at Processor.process (C:\Users\tbmst\Documents\svelte-template-webpack-typescript\node_modules\postcss\lib\processor.js:28:14)
    at Object.loader (C:\Users\tbmst\Documents\svelte-template-webpack-typescript\node_modules\postcss-loader\dist\index.js:97:30)

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const Dotenv = require("dotenv-webpack");
const sveltePreprocess = require("svelte-preprocess");
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");
const TerserPlugin = require("terser-webpack-plugin");
const production = false;
const DotenvWebpackPlugin = require("dotenv-webpack");
const ESLintPlugin = require("eslint-webpack-plugin");
const { createSveltePreprocessor } = require("./svelte.config.js");
//const { InjectManifest } = require("workbox-webpack-plugin");
const { ModuleKind } = require("typescript");

process.env.ARGS = JSON.stringify(process.argv.splice(2));
const serve = process.env.ARGS[0] === "serve" || process.env.ARGS[0] === "dev";

// Can be async
const minify = (input, sourceMap, minimizerOptions, extractsComments) => {
  // The `minimizerOptions` option contains option from the `terserOptions` option
  // You can use `minimizerOptions.myCustomOption`

  // Custom logic for extract comments
  const { map, code } = require("uglify-js") // Or require('./path/to/uglify-module')
    .minify(input, {
      /* Your options for minification */
    });

  return { map, code, warnings: [], errors: [], extractedComments: [] };
};

// Used to regenerate `fullhash`/`chunkhash` between different implementation
// Example: you fix a bug in custom minimizer/custom function, but unfortunately webpack doesn't know about it, so you will get the same fullhash/chunkhash
// to avoid this you can provide version of your custom minimizer
// You don't need if you use only `contenthash`
minify.getMinimizerVersion = () => {
  let packageJson;

  try {
    // eslint-disable-next-line global-require, import/no-extraneous-dependencies
    packageJson = require("uglify-js/package.json");
  } catch (error) {
    // Ignore
  }

  return packageJson && packageJson.version;
};

const devServer = {
  port: 5000,
  host: "local.node.flipkick.media",
  server: {
    type: "spdy",
    options: {
      http2: true,
      key: "./local.node.flipkick.media/privkey.pem",
      cert: "./local.node.flipkick.media/cert.pem",
    },
  },
  compress: true,
  historyApiFallback: true,
  hot: true,
};

module.exports = {
  mode: "development",
  entry: "./src/main.ts",

  output: {
    assetModuleFilename: "[hash][ext][query]",
    asyncChunks: true,
    path: path.resolve(__dirname, "build"),
    filename: "[name].[contenthash:8].js",
    sourceMapFilename: "[name].[contenthash:8].map",
    chunkFilename: "[id].[contenthash:8].js",
    clean: true,
  },
  optimization: {
    moduleIds: "deterministic",
    runtimeChunk: "single",
    usedExports: true,
    mergeDuplicateChunks: true,
    splitChunks: {
      chunks: "all",
      maxInitialRequests: Infinity,
      minSize: 0,
      cacheGroups: {
        entry: {
          name(module) {
            let packageName = module.context.match(/.*[\\/]([a-zA-Z0-9-_@]+)$/);

            if (packageName === null) {
              packageName ==
                packageName[1].replace(/[\@\\.]/gi, "-").replace(/^-/, "");
            }
            return packageName[1];
          },
        },
        main: {
          name(module) {
            let packageName = module.context.match(/.*[\\/]([a-zA-Z0-9-_@]+)$/);

            if (packageName === null) {
              packageName ==
                packageName[1].replace(/[\@\\.]/gi, "-").replace(/^-/, "");
            }
            return packageName[1];
          },
        },
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            let packageName = module.context.match(/.*[\\/]([a-zA-Z0-9-_@]+)$/);

            if (packageName === null) {
              packageName ==
                packageName[1].replace(/[\@\\.]/gi, "-").replace(/^-/, "");
            }
            return packageName[1];
          },
        },
      },
    },

    minimize: false,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          myCustomOption: true,
          compress: {
            global_defs: {
              "@console.log": "alert",
            },
            passes: 2,
          },
          format: {
            preamble: "/* minified */",
          },
        },
        minify,
      }),
    ],
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
      "@components": path.resolve(__dirname, "src/components"),
      "@lib": path.resolve(__dirname, "src/lib"),
      "@proto": path.resolve(__dirname, "src/proto"),
      svelte: path.resolve("node_modules", "svelte"),
      util: path.resolve("node_modules", "util"),
    },
    mainFields: ["svelte", "browser", "module", "main", ".svelte"],
    extensions: [".mjs", ".js", ".ts", ".json"],
  },
  devtool: "eval-source-map",
  module: {
    rules: [
      {
        test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
        use: [
          {
            loader: "file-loader",
            options: {
              name: "[name].[ext]",
              outputPath: "fonts/",
            },
          },
        ],
      },
      {
        test: /\.ts?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
      {
        test: /\.svelte$/,
        loader: "svelte-loader",
        options: {
          compilerOptions: {
            css: false,
          },
          emitCss: true,
          preprocess: createSveltePreprocessor(),
        },
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
      },
      {
        test: /\.(jpg|jpeg|png|svg)$/,
        use: "url-loader",
      },
      {
        // required to prevent errors from Svelte on Webpack 5+
        test: /node_modules\/svelte\/.*\.mjs$/,
        resolve: {
          fullySpecified: false,
        },
      },
    ],
  },
  mode: "development",
  plugins: [
    new DotenvWebpackPlugin({
      allowEmptyValues: false,
      safe: true,
      systemvars: false,
      silent: false,
      expand: false,
      ignoreStub: true,
    }),
    new HtmlWebpackPlugin({
      template: "./public/index.html",
      cache: false,
      title: "Svelte App",
    }),
    new MiniCssExtractPlugin({}),
    new ESLintPlugin({
      failOnError: true,
      failOnWarning: false,
      emitError: true,
      emitWarning: true,
    }),
    new Dotenv(),
  ],
  devServer,
};
flipkickmedia commented 2 years ago

I've isolated the issue. It appears that the sveltePreprocess config needed an additional statement to reloading work. Is it expected to build on the first run?

I am seeing additional output now from the build process about unused selectors so I assume it was working because the CSS was plain, only when it was seeing a postcss import on a rebuild would it fail.

const sveltePreprocess = require("svelte-preprocess");

const createSveltePreprocessor = () => {
  return sveltePreprocess({
    tsconfigFile: "tsconfig.json",
    sourceMap: true,
    postcss: true   // this was missing from the config, adding this causes subsequent rebuilds to work correctly
  });
};

module.exports = {
  preprocess: createSveltePreprocessor(),
  createSveltePreprocessor,
};