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

Support passthrough dependency request. #944

Open
ScriptedAlchemy opened this issue May 17, 2022 · 14 comments
Open

Support passthrough dependency request. #944

ScriptedAlchemy opened this issue May 17, 2022 · 14 comments

Comments

@ScriptedAlchemy
Copy link
Contributor

ScriptedAlchemy commented May 17, 2022

Feature Proposal

Feature Use Case

I want to use webpack to bundle npm packages and component libraries that come with css/scss.
Currently, mini-css treats css resources as side effects. Which makes sense when webpack is the consumer build, but does not make sense if webpack is a library build.

Id like to still process styles, but leave the require statement as an external.

example:
require('./button.scss') -> becomes document.createElement(handleSideEffectDep)
for library builds id like to
require('./button.scss') -> require('./dist/248.css') so that the consumer build manages the side effect and i can ship component libraries that require their own css, but do not handle DOM injection.

Currently you would have to use style-loader which is not optimal. The alternative avaliable is using something like rollup which does not attempt to bundle side effects of a library, instead it treats it like an external once emitted. Then the consumer build is responsible for loading the side effect and its runtime requirements to inject styles.

Current output:

;// CONCATENATED MODULE: ./src/component.css
// extracted by mini-css-extract-plugin
/* harmony default export */ const component = ({"test-css-loader":"gj_ovF"});

desired output

;// CONCATENATED MODULE: ./src/component.css
// extracted by mini-css-extract-plugin
require('dist/248.css') // REFERENCE MODULE: ./src/component.css
/* harmony default export */ const component = ({"test-css-loader":"gj_ovF"});

If i add a very primitive loader to the start of the loader chain, it kind of works (though resolution paths would be incorrect. But that could be fixed.

module.exports = function(content,map,meta) {
    // console.log(this.resourcePath);
    // console.log(content);
    // content.source = 'test'
    const result = `__non_webpack_require__('${this.resourcePath}');
    ${content}`

    console.log(result)
    this.callback(null,result)
}

that would process the file as i need, and still load a require() into the file as a side effect - however this should point to main.css to pair with main.mjs/js

Please paste the results of npx webpack-cli info here, and mention other relevant information

@ScriptedAlchemy
Copy link
Contributor Author

Seemingly related but I feel this could be conveniently supported by minicss. Something along the lines of an option to use ExternalModule instead of CSSModule within the plugin

#95

@alexander-akait
Copy link
Member

Sounds good, I think we can do it under option @sokra @vankop What do you think?

@ScriptedAlchemy
Copy link
Contributor Author

Yeah like on the Loader Options

type: "external"
or
external: true which would indicate "require/import the emitted asset around where locals usually is written to. This would only be intended for package authoring, not really for "last mile" builds

@alexander-akait
Copy link
Member

alexander-akait commented May 18, 2022

Yes, let's wait answers and we will decide how best to design/implement it

@ScriptedAlchemy
Copy link
Contributor Author

Fantastic! Fully willing to contribute on this one as well!

@ajayjaggi97
Copy link

How will this work during SSR?? @ScriptedAlchemy

@ScriptedAlchemy
Copy link
Contributor Author

Consumer build would be in charge of last mile. So css loader / mini css would strip the imports out and just leave the local exports. Same way it works today. Just slight differentiation between packaging via final build by consumer

@ScriptedAlchemy
Copy link
Contributor Author

Update on this.

My team took my loader idea mentioned above and added an extra loader before mini css. Then using contextify we are able to inject a require or import() depending on if its esm or cjs.

Not sure how we could implement import since im depending on magic comments or non webpack require to prevent it getting extracted again.

Bit hacky but it seems to work for the time being while this issue is being investigated

@alexander-akait
Copy link
Member

@ScriptedAlchemy Feel free to send a PR (with any solutions) so we can dicussion on code

@mikeechen
Copy link

Hello! This is what we wrote on loading the field's css output. I took the mainFields value to construct the css path, since I think that's what this plugin is using to generate the css file. We have a couple checks just for browser targets and modules in order to pass the right fields in. Seems to work for our case, and would love feedback on it!

module.exports = function (content) {
  const { utils, _compiler: compiler } = this;

  if (!compiler.options.output.library && compiler.options.target === "web") {
    this.callback(null, content);
    return;
  }

  const mainFields = compiler.options.resolve.mainFields;
  const outputPath = (field) =>
    utils.contextify(compiler.outputPath, `./${field}.css`);

  let result;

  if (compiler.options.output.module) {
    result = mainFields.map((fieldString) => {
      return `import(/* webpackIgnore: true */ '${outputPath(fieldString)}')`;
    });
  } else {
    result = mainFields.map((fieldString) => {
      return `__non_webpack_require__('${outputPath(fieldString)}')`;
    });
  }
  result.push(content);

  this.callback(null, result.join(";"));
};

@ScriptedAlchemy
Copy link
Contributor Author

@alexander-akait any suggestion on how we could implement. I’m happy to send a PR if you have a hint or sample of what/where I should start

@alexander-akait
Copy link
Member

@ScriptedAlchemy Do you have any solution? If yes, let's add the option and feel free to send a PR with code that you have. Exampe above is good, we need the same

@ScriptedAlchemy
Copy link
Contributor Author

Excellent I'll start tomorrow with my team. I see it as need for emit asset, use mini css loader to handle exports. Then take js and emit it, then inject the require into the file with the css module maps.

@ScriptedAlchemy
Copy link
Contributor Author

Okay im going to try and factor this into mini-css tomorrow.
But this loader "mostly" works - its messy and needs cleanup, but I just got the desired result out of the build.
Note i set externals to /external\.css/ so webpack doesnt go into an import loop

const AUTO_PUBLIC_PATH = "__mini_css_extract_plugin_public_path_auto__";
const BASE_URI = "webpack://";
const { getOptions, interpolateName } = require("loader-utils");
const path = require("path");
const { normalizePath } = require("./utils");
const handleExports = (exports) => {
  const result = exports.default || exports;
  return {
    content: result.toString(),
    locals: result.locals,
  };
};
async function pitch(content) {
  const options = {};
  const callback = this.async();
  let { publicPath, module } =
    /** @type {Compilation} */
    (this._compilation).outputOptions;
  let loaderArray = this.request.split("!");
  loaderArray.shift();
  const nonCircularLoader = loaderArray.join("!");

  const result = await this.importModule(
    `${this.resourcePath}.webpack[javascript/auto]!=!!!${nonCircularLoader}`
  );
  const handledResult = handleExports(result);

  let loaderResult = [];

  const context = options.context || this.rootContext;
  const name = options.name || "[contenthash].external.css";

  const url = interpolateName(this, name, {
    context,
    content,
    regExp: options.regExp,
  });
  if (typeof options.emitFile === "undefined" || options.emitFile) {
    const assetInfo = {};

    if (typeof name === "string") {
      let normalizedName = name;

      const idx = normalizedName.indexOf("?");

      if (idx >= 0) {
        normalizedName = normalizedName.substr(0, idx);
      }

      const isImmutable = /\[([^:\]]+:)?(hash|contenthash)(:[^\]]+)?]/gi.test(
        normalizedName
      );

      if (isImmutable === true) {
        assetInfo.immutable = true;
      }
    }

    assetInfo.sourceFilename = normalizePath(
      path.relative(this.rootContext, this.resourcePath)
    );

    console.log(assetInfo);
    console.log(url);

    this.emitFile(url, handledResult.content, null, assetInfo);
  }
  if (module) {
    loaderResult.push(`import '${url}'`);
    loaderResult.push("export default " + JSON.stringify(handledResult.locals));
  } else {
    loaderResult.push(`require('${url}')`);
    loaderResult.push(
      "module.exports = " + JSON.stringify(handledResult.locals)
    );
  }
  this.callback(null, loaderResult.join(";"));
}
module.exports = {
  default:   pitch, ///TODO: actually use loader.pitch instead of loader
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants