Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Child compiler target is still "browser" (should be "node") #1590

Closed
braska opened this issue Jan 28, 2021 · 7 comments
Closed

Child compiler target is still "browser" (should be "node") #1590

braska opened this issue Jan 28, 2021 · 7 comments
Labels

Comments

@braska
Copy link

braska commented Jan 28, 2021

Current behaviour 💣

I would like to use JSX as template. I created myTemplate.jsx file:

import styled, { ServerStyleSheet } from "styled-components";
import { renderToStaticMarkup } from "react-dom/server";

const StyledDiv = styled.div`
  width: 100px;
  height: 100px;
  background-color: red;
`;

const sheet = new ServerStyleSheet();

const content = renderToStaticMarkup(sheet.collectStyles(<StyledDiv />));

const html = `<!DOCTYPE html><html><head>${sheet.getStyleTags()}</head><body>${content}asdasd</body></html>`;
sheet.seal();

export default html;

And I pointed this file in plugin options in Webpack config:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');

const NODE_ENV = process.env.NODE_ENV || 'development';

module.exports = {
  context: path.resolve(__dirname, 'src'),
  entry: './main.js',
  mode: NODE_ENV,
  devtool: NODE_ENV === 'production' ? 'source-map' : 'eval-source-map',
  resolve: {
    extensions: ['.js', '.jsx', '.json'],
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      { test: /\.jsx?$/, use: 'babel-loader', exclude: /node_modules/, },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: `./myTemplate.jsx`,
    }),
    new CleanWebpackPlugin(),
    new CopyPlugin({
      patterns: [{ from: 'static', to: '../dist' }],
    }),
  ],
  devServer: {
    host: 'localhost',
    open: true,
  },
};

But I see this error on compilation:

ReferenceError: window is not defined
  
  - styled-components.browser.esm.js:29 eval
    [..]/[styled-components]/dist/styled-components.browser.esm.js:29:26231
  
  - myTemplate.jsx:336 Object.../node_modules/styled-components/dist/styled-components.browser.esm.js
    /path/to/myTemplate.jsx:336:1

As I see styled-components.browser.esm.js was used. HtmlWebpackPlugin compiles template in child compailer and later runs compiled JS in Node (by vm package). That means target for child compilation should be "node". But for some reason it still "browser". And bacause of this styled-components.browser.esm.js was used.

Expected behaviour ☀️

styled-components has a lot of JS files declared in package.json:

{
  "main": "dist/styled-components.cjs.js",
  "jsnext:main": "dist/styled-components.esm.js",
  "module": "dist/styled-components.esm.js",
  "react-native": "native/dist/styled-components.native.cjs.js",
  "browser": {
    "./dist/styled-components.esm.js": "./dist/styled-components.browser.esm.js",
    "./dist/styled-components.cjs.js": "./dist/styled-components.browser.cjs.js"
  }
}

I think dist/styled-components.cjs.js should be used. Proably, Webpack will automatically use it if target in child compiler set to "node".

Found workaround

If you add target: "node" to webpack config, that child compiler inherits this target. It fixes template compilation, but breaks my entrypoint (it requires target: "browser").

I temporary add this code to here

childCompiler.options.externalsPresets = {
  web: false,
  node: true,
  nwjs: false,
  electron: false,
  electronMain: false,
  electronPreload: false,
  electronRenderer: false,
};
childCompiler.options.loader = {
  target: "node",
};
childCompiler.options.target = "node";
childCompiler.options.node = {
  global: false,
  __filename: "eval-only",
  __dirname: "eval-only",
};
childCompiler.options.output = {
  ...childCompiler.options.output,
  chunkFormat: "commonjs",
  chunkLoading: "require",
  enabledChunkLoadingTypes: ["require"],
  enabledWasmLoadingTypes: ["async-node"],
  globalObject: "global",
  wasmLoading: "async-node",
  workerChunkLoading: "require",
  workerWasmLoading: "async-node",
};
childCompiler.options.resolve.conditionNames = [
  "webpack",
  "development",
  "node",
];
childCompiler.options.resolve.byDependency = {
  wasm: {
    conditionNames: ["import", "module", "..."],
    aliasFields: [],
    mainFields: ["module", "..."],
  },
  esm: {
    conditionNames: ["import", "module", "..."],
    aliasFields: [],
    mainFields: ["module", "..."],
  },
  worker: {
    conditionNames: ["import", "module", "..."],
    aliasFields: [],
    mainFields: ["module", "..."],
    preferRelative: true,
  },
  commonjs: {
    conditionNames: ["require", "module", "..."],
    aliasFields: [],
    mainFields: ["module", "..."],
  },
  amd: {
    conditionNames: ["require", "module", "..."],
    aliasFields: [],
    mainFields: ["module", "..."],
  },
  loader: {
    conditionNames: ["require", "module", "..."],
    aliasFields: [],
    mainFields: ["module", "..."],
  },
  unknown: {
    conditionNames: ["require", "module", "..."],
    aliasFields: [],
    mainFields: ["module", "..."],
  },
  undefined: {
    conditionNames: ["require", "module", "..."],
    aliasFields: [],
    mainFields: ["module", "..."],
  },
  url: {
    preferRelative: true,
  },
};

This code patches child compiler configuration and everything works as expected. But it is dirty hack just to prove my idea that problem related to target in child compiler.

Reproduction Example 👾

https://codesandbox.io/s/html-webpack-plugin-5x-alpha-forked-4g5yf?file=/src/template.jsx

Environment 🖥

Node.js v15.7.0
webpack@5.18.0
html-webpack-plugin@5.0.0-beta.6

@jantimon
Copy link
Owner

That's really amazing :)

Would be interesting to if there is also a way which will be easier to maintain in future..

@braska
Copy link
Author

braska commented Jan 30, 2021

Thanks @jantimon for quick response!

I am very interested in using JSX templates.

  • It allows me to use things like CSS-in-JS. With styled-components it gives Critical CSS without extra effort.
  • I can use react-helmet to manipulate <head>...</head>

It feels like build-time server-side rendering (unlike traditional SSR that works in runtime).

But unfortunately I don't have experience with Webpack plugin development. So I can't fix HtmlWebpackPlugin plugin to allow it properly build JSX.

What I found out:

  • applying suggested patch (which changes childCompiler.options) affects main compilation
    • import styled from 'styled-components' somewhere in template works correctly (dist/styled-components.esm.js imported)
    • import styled from 'styled-components' somewhere in entrypoint (file which I pointed in my main webpack.config.js in entry field) imports wrong file (it should import ./dist/styled-components.browser.esm.js, but instead dist/styled-components.esm.js imported)
  • without applying patch (just using latest beta version of HtmlWebpackPlugin)
    • import styled from 'styled-components' somewhere in template imports ./dist/styled-components.browser.esm.js, but dist/styled-components.esm.js should be imported.
    • import styled from 'styled-components' somewhere in entrypoint works correctly (./dist/styled-components.browser.esm.js imported)

So suggested patch is not a solution. We need to find proper solution.

I see that NodeTargetPlugin applied to child compiler (in lib/child-compiler.js) and I am actually surprised that this plugin doesn't actually change target to node. Maybe there is another internal wepback plugin that can switch compilation target to node?

@braska
Copy link
Author

braska commented Jan 30, 2021

I posted this question to Webpack repo: webpack/webpack#12539

@jantimon
Copy link
Owner

@braska I added the NodeTemplatePlugin for html-webpack-plugin@5.2.0 - can you please try if this helps for your case?

@braska
Copy link
Author

braska commented Feb 19, 2021

@jantimon Unfortunately it doesn't work :(

You can use this CodeSandbox to see error: https://codesandbox.io/s/html-webpack-plugin-5x-alpha-forked-4g5yf?file=/src/template.jsx (I updated html-webpack-plugin to 5.2.0 in this CodeSandbox)


As a temp solution I am using this hack: https://gist.github.com/braska/ae753c26a31b01a61539f867a2cedfc7

I wrote this plugin and added it before html-webpack-plugin in my webpack.config.js.

@hagevvashi
Copy link

hagevvashi commented Apr 5, 2021

When using above patch (https://gist.github.com/braska/ae753c26a31b01a61539f867a2cedfc7) with optimization.splitChunks, there happens an Error:

Error: internal/modules/cjs/loader.js:883
   throw err;
   ^
Error: Cannot find module './__child-189'

.

This error is caused in the line below.

newSource = vmScript.runInContext(vmContext);

As a workaround for this error, I remove SplitChunksPlugin from childCompiler.hooks.thisCompilation.taps.

Here is the diff from the patch @braska created.

diff --git a/PatchHtmlWebpackPlugin.js b/PatchHtmlWebpackPlugin.js
index 8fec424..ed1ea74 100644
--- a/PatchHtmlWebpackPlugin.js
+++ b/PatchHtmlWebpackPlugin.js
@@ -4,6 +4,13 @@
 const WebpackOptionsApply = require("webpack/lib/WebpackOptionsApply");
 const ResolverFactory = require("webpack/lib/ResolverFactory");

+function removeTapFromHook(hooks, tapName) {
+  const index = hooks.taps.findIndex(tap => tap.name === tapName);
+  if (index > -1) {
+      hooks.taps.splice(index, 1);
+  }
+}
+
 class PatchHtmlWebpackPluginPlugin {
   apply(compiler) {
     compiler.hooks.compilation.tap(
@@ -111,6 +118,24 @@ class PatchHtmlWebpackPluginPlugin {
               optionsCopy,
               childCompiler
             );
+
+            // workaround for
+            //   Error: internal/modules/cjs/loader.js:883
+            //     throw err;
+            //     ^
+            //   Error: Cannot find module './__child-189'
+            // happens in
+            // https://github.com/jantimon/html-webpack-plugin/blob/0a6568d587a82d88fd3a0617234ca98d26a1e0a6/index.js#L142
+            [
+              // "CommonJsChunkFormatPlugin",
+              // "ReadFileCompileWasmPlugin",
+              // "ReadFileCompileAsyncWasmPlugin",
+              // "WorkerPlugin",
+              "SplitChunksPlugin",
+              // "ResolverCachePlugin"
+            ].forEach((tapName) => {
+              removeTapFromHook(childCompiler.hooks.thisCompilation, tapName);
+            })
           }
         );
       }

updated

This happens especially when using react component for the template.

@stale
Copy link

stale bot commented Apr 16, 2022

This issue had no activity for at least half a year. It's subject to automatic issue closing if there is no activity in the next 15 days.

@stale stale bot added the wontfix label Apr 16, 2022
@stale stale bot closed this as completed May 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants