mini-css-extract-plugin
mini-css-extract-plugin copied to clipboard
Support passthrough dependency request.
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
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
https://github.com/webpack-contrib/mini-css-extract-plugin/issues/95
Sounds good, I think we can do it under option @sokra @vankop What do you think?
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
Yes, let's wait answers and we will decide how best to design/implement it
Fantastic! Fully willing to contribute on this one as well!
How will this work during SSR?? @ScriptedAlchemy
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
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
@ScriptedAlchemy Feel free to send a PR (with any solutions) so we can dicussion on code
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(";"));
};
@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
@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
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.
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
};