Skip to content

Commit

Permalink
feat: add function support for locals (loader) (#985)
Browse files Browse the repository at this point in the history
  • Loading branch information
yungvldai committed Nov 16, 2022
1 parent 418fd09 commit 65519d0
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 6 deletions.
13 changes: 7 additions & 6 deletions src/loader.js
Expand Up @@ -8,6 +8,7 @@ const {
BASE_URI,
SINGLE_DOT_PATH_SEGMENT,
stringifyRequest,
stringifyLocal,
} = require("./utils");
const schema = require("./loader-options.json");

Expand All @@ -22,6 +23,7 @@ const MiniCssExtractPlugin = require("./index");
/** @typedef {import("webpack").AssetInfo} AssetInfo */
/** @typedef {import("webpack").NormalModule} NormalModule */
/** @typedef {import("./index.js").LoaderOptions} LoaderOptions */
/** @typedef {{ [key: string]: string | function }} Locals */

/** @typedef {any} TODO */

Expand All @@ -38,7 +40,7 @@ const MiniCssExtractPlugin = require("./index");

/**
* @param {string} content
* @param {{ loaderContext: import("webpack").LoaderContext<LoaderOptions>, options: LoaderOptions, locals: {[key: string]: string } | undefined }} context
* @param {{ loaderContext: import("webpack").LoaderContext<LoaderOptions>, options: LoaderOptions, locals: Locals | undefined }} context
* @returns {string}
*/
function hotLoader(content, context) {
Expand Down Expand Up @@ -95,7 +97,7 @@ function pitch(request) {
* @returns {void}
*/
const handleExports = (originalExports, compilation, assets, assetsInfo) => {
/** @type {{[key: string]: string } | undefined} */
/** @type {Locals | undefined} */
let locals;
let namedExport;

Expand Down Expand Up @@ -170,7 +172,7 @@ function pitch(request) {
locals = {};
}

locals[key] = originalExports[key];
/** @type {Locals} */ (locals)[key] = originalExports[key];
}
});
} else {
Expand Down Expand Up @@ -228,9 +230,8 @@ function pitch(request) {
? Object.keys(locals)
.map(
(key) =>
`\nexport var ${key} = ${JSON.stringify(
/** @type {{[key: string]: string }} */
(locals)[key]
`\nexport var ${key} = ${stringifyLocal(
/** @type {Locals} */ (locals)[key]
)};`
)
.join("")
Expand Down
10 changes: 10 additions & 0 deletions src/utils.js
Expand Up @@ -205,6 +205,15 @@ function getUndoPath(filename, outputPath, enforceRelative) {
: append;
}

/**
*
* @param {string | function} value
* @returns {string}
*/
function stringifyLocal(value) {
return typeof value === "function" ? value.toString() : JSON.stringify(value);
}

module.exports = {
trueFn,
findModuleById,
Expand All @@ -216,5 +225,6 @@ module.exports = {
BASE_URI,
SINGLE_DOT_PATH_SEGMENT,
stringifyRequest,
stringifyLocal,
getUndoPath,
};
4 changes: 4 additions & 0 deletions test/cases/custom-loader-with-functional-exports/app/index.js
@@ -0,0 +1,4 @@
import { cnA, cnB } from "./style.css";

// eslint-disable-next-line no-console
console.log(cnA(), cnB());
14 changes: 14 additions & 0 deletions test/cases/custom-loader-with-functional-exports/app/mockLoader.js
@@ -0,0 +1,14 @@
export default function loader() {
const callback = this.async();

callback(
null,
`export default [
[module.id, ".class-name-a {background: red;}", ""],
[module.id, ".class-name-b {background: blue;}", ""],
];
export var cnA = () => "class-name-a";
export var cnB = () => "class-name-b";`
);
}
@@ -0,0 +1,7 @@
.class-name-a {
background: red;
}

.class-name-b {
background: blue;
}
@@ -0,0 +1,2 @@
.class-name-a {background: red;}
.class-name-b {background: blue;}
87 changes: 87 additions & 0 deletions test/cases/custom-loader-with-functional-exports/expected/main.js
@@ -0,0 +1,87 @@
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ([
/* 0 */,
/* 1 */
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "cnA": () => (/* binding */ cnA),
/* harmony export */ "cnB": () => (/* binding */ cnB)
/* harmony export */ });
// extracted by mini-css-extract-plugin
var cnA = () => "class-name-a";
var cnB = () => "class-name-b";

/***/ })
/******/ ]);
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _style_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);


// eslint-disable-next-line no-console
console.log((0,_style_css__WEBPACK_IMPORTED_MODULE_0__.cnA)(), (0,_style_css__WEBPACK_IMPORTED_MODULE_0__.cnB)());

})();

/******/ })()
;
21 changes: 21 additions & 0 deletions test/cases/custom-loader-with-functional-exports/webpack.config.js
@@ -0,0 +1,21 @@
import path from "path";

import Self from "../../../src";

module.exports = {
entry: "./index.js",
context: path.resolve(__dirname, "app"),
module: {
rules: [
{
test: /\.css$/,
use: [Self.loader, "./mockLoader"],
},
],
},
plugins: [
new Self({
filename: "[name].css",
}),
],
};
25 changes: 25 additions & 0 deletions test/stringifyLocal.test.js
@@ -0,0 +1,25 @@
import { stringifyLocal } from "../src/utils";

describe("stringifyLocal", () => {
it(`primitive`, async () => {
const testObj = "classA";

expect(stringifyLocal(testObj)).toBe('"classA"');
});

it(`arrow function`, async () => {
const testFn = () => "classA";

expect(stringifyLocal(testFn)).toBe('() => "classA"');
});

it(`function`, async () => {
const testFn = function () {
return "classA";
};

expect(stringifyLocal(testFn)).toBe(
'function () {\n return "classA";\n }'
);
});
});

0 comments on commit 65519d0

Please sign in to comment.