├── .gitignore ├── lib ├── index.d.ts ├── utils.d.ts ├── jsx-loader.d.ts ├── css-loader.d.ts ├── @types.d.ts ├── utils.js ├── css-loader.js ├── index.js └── jsx-loader.js ├── .DS_Store ├── src ├── @types.ts ├── css-loader.ts ├── utils.ts ├── jsx-loader.ts └── index.ts ├── LICENSE.MD ├── tsconfig.json ├── package.json ├── README.MD └── rollup.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /lib/utils.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /lib/jsx-loader.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayu888/react-css-module-plugin/HEAD/.DS_Store -------------------------------------------------------------------------------- /lib/css-loader.d.ts: -------------------------------------------------------------------------------- 1 | declare const crosstree: any; 2 | declare const chalk: any; 3 | declare const log: { 4 | (...data: any[]): void; 5 | (message?: any, ...optionalParams: any[]): void; 6 | }; 7 | -------------------------------------------------------------------------------- /src/@types.ts: -------------------------------------------------------------------------------- 1 | import { Compiler } from 'webpack'; 2 | 3 | export interface ScopeJsxCssPluginI{ 4 | isReg:(r: any) => boolean; 5 | apply:(compiler :Compiler) => void; 6 | } 7 | 8 | export type PreStyle = '.less' | '.scss' | '.sass' | '.css'; 9 | export type PreFile = 'js' | 'jsx' | 'ts' | 'tsx'; 10 | export type Cludes = string | string[]; 11 | 12 | export interface Options{ 13 | preStyle: PreStyle; 14 | preFile: PreFile[] | PreFile; 15 | includes?: Cludes; 16 | excludes?: Cludes; 17 | }; 18 | 19 | export interface JsConfig{ 20 | test: RegExp; 21 | exclude: RegExp; 22 | loader: string; 23 | options:{ 24 | excludes?: Cludes 25 | includes?: Cludes, 26 | } 27 | } -------------------------------------------------------------------------------- /lib/@types.d.ts: -------------------------------------------------------------------------------- 1 | import { Compiler } from 'webpack'; 2 | export interface ScopeJsxCssPluginI { 3 | isReg: (r: any) => boolean; 4 | apply: (compiler: Compiler) => void; 5 | } 6 | export declare type PreStyle = '.less' | '.scss' | '.sass' | '.css'; 7 | export declare type PreFile = 'js' | 'jsx' | 'ts' | 'tsx'; 8 | export declare type Cludes = string | string[]; 9 | export interface Options { 10 | preStyle: PreStyle; 11 | preFile: PreFile[] | PreFile; 12 | includes?: Cludes; 13 | excludes?: Cludes; 14 | } 15 | export interface JsConfig { 16 | test: RegExp; 17 | exclude: RegExp; 18 | loader: string; 19 | options: { 20 | excludes?: Cludes; 21 | includes?: Cludes; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var preFileTransFormReg = function (preFile) { 4 | var test; 5 | if (Array.isArray(preFile)) { 6 | test = preFile.join('|'); 7 | } 8 | if (typeof (preFile) === 'string') { 9 | test = preFile; 10 | } 11 | test = new RegExp("\\.(" + test + ")$"); 12 | return test; 13 | }; 14 | var styleType = [".less", ".scss", ".sass", ".css"]; 15 | var fileType = ["js", "jsx", "tsx", "ts"]; 16 | var preMap = { 17 | '.less': 'less-loader', 18 | '.scss': 'sass-loader', 19 | '.sass': 'sass-loader', 20 | '.css': 'css-loader', 21 | }; 22 | var defaultConfig = { 23 | sourceType: "module", 24 | plugins: ["dynamicImport", "jsx", "classProperties", "typescript"], 25 | }; 26 | module.exports = { 27 | preFileTransFormReg: preFileTransFormReg, 28 | styleType: styleType, 29 | fileType: fileType, 30 | preMap: preMap, 31 | defaultConfig: defaultConfig, 32 | }; 33 | -------------------------------------------------------------------------------- /src/css-loader.ts: -------------------------------------------------------------------------------- 1 | 2 | const crosstree = require('css-tree'); 3 | const chalk = require('chalk'); 4 | const log = console.log; 5 | 6 | module.exports = function (source:T): T{ 7 | const self:any = this; 8 | if (!self.resource) return source; 9 | try { 10 | const query = self.resource.split('?')[1]; 11 | if (!query || !Object.keys(query).length) return source; 12 | const resourceQuery = JSON.parse(query); 13 | const ast = crosstree.parse(source); 14 | crosstree.walk(ast, { 15 | visit: 'ClassSelector', 16 | enter(node: any) { 17 | if (resourceQuery[node.name]) { 18 | node._postfix = `${resourceQuery[node.name]}`; 19 | } 20 | }, 21 | leave(node: any) { 22 | node.name = node._postfix ? node._postfix : node.name; 23 | } 24 | }); 25 | return crosstree.generate(ast); 26 | } catch (err) { 27 | log(chalk.red(err)); 28 | return source; 29 | } 30 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { ParserOptions } from '@babel/parser'; 2 | import { PreStyle, PreFile } from './@types'; 3 | const preFileTransFormReg = (preFile: string | string[]): RegExp => { 4 | let test; 5 | if (Array.isArray(preFile)) { 6 | test = preFile.join('|'); 7 | } 8 | if (typeof (preFile) === 'string') { 9 | test = preFile; 10 | } 11 | test = new RegExp(`\\.(${test})$`); 12 | return test; 13 | } 14 | 15 | const styleType: PreStyle[] = [".less", ".scss", ".sass", ".css"]; 16 | 17 | const fileType: PreFile[] = ["js", "jsx", "tsx", "ts"]; 18 | 19 | const preMap = { 20 | '.less': 'less-loader', 21 | '.scss': 'sass-loader', 22 | '.sass': 'sass-loader', 23 | '.css': 'css-loader', 24 | } 25 | 26 | const defaultConfig: ParserOptions = { 27 | sourceType: "module", 28 | plugins: ["dynamicImport", "jsx", "classProperties", "typescript"], 29 | } 30 | 31 | 32 | module.exports = { 33 | preFileTransFormReg, 34 | styleType, 35 | fileType, 36 | preMap, 37 | defaultConfig, 38 | } -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 mayu888 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/css-loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var crosstree = require('css-tree'); 4 | var chalk = require('chalk'); 5 | var log = console.log; 6 | module.exports = function (source) { 7 | var self = this; 8 | if (!self.resource) 9 | return source; 10 | try { 11 | var query = self.resource.split('?')[1]; 12 | if (!query || !Object.keys(query).length) 13 | return source; 14 | var resourceQuery_1 = JSON.parse(query); 15 | var ast = crosstree.parse(source); 16 | crosstree.walk(ast, { 17 | visit: 'ClassSelector', 18 | enter: function (node) { 19 | if (resourceQuery_1[node.name]) { 20 | node._postfix = "" + resourceQuery_1[node.name]; 21 | } 22 | }, 23 | leave: function (node) { 24 | node.name = node._postfix ? node._postfix : node.name; 25 | } 26 | }); 27 | return crosstree.generate(ast); 28 | } 29 | catch (err) { 30 | log(chalk.red(err)); 31 | return source; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "./lib", 5 | "module": "ESNext", 6 | "target": "es5", 7 | "lib": [ 8 | "es6", 9 | "es2016", 10 | "es2017", 11 | "es2018", 12 | "es2019", 13 | "es2020", 14 | "dom" 15 | ], 16 | "strict": true, 17 | "esModuleInterop": true, 18 | "allowSyntheticDefaultImports": true, 19 | "downlevelIteration":true, 20 | "declaration": true, 21 | "declarationDir": "./lib", 22 | "sourceMap": false, 23 | "allowJs": true, 24 | "moduleResolution": "node", 25 | // "rootDir": "src", 26 | "forceConsistentCasingInFileNames": true, 27 | "noImplicitReturns": true, 28 | "noImplicitThis": true, 29 | "noImplicitAny": true, 30 | "strictNullChecks": true, 31 | "suppressImplicitAnyIndexErrors": true, 32 | "noUnusedLocals": true, 33 | // "types": ["reflect-metadata"], 34 | "experimentalDecorators": true, 35 | "emitDecoratorMetadata": true, 36 | "typeRoots": [ 37 | "./node_modules/@types", 38 | "./lib/@types", 39 | ] 40 | }, 41 | "skipLibCheck": true, 42 | "include": [ 43 | "src", 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-css-module-plugin", 3 | "version": "1.0.7", 4 | "description": "none", 5 | "repository": "https://github.com/mayu888/react-css-module-plugin.git", 6 | "author": "mayu827@163.com", 7 | "main": "./lib/index.js", 8 | "license": "MIT", 9 | "scripts": { 10 | "flow": "flow", 11 | "build": "rollup -c rollup.config.js" 12 | }, 13 | "files": [ 14 | "lib", 15 | "README.MD", 16 | "LICENSE.MD" 17 | ], 18 | "dependencies": { 19 | "@babel/parser": "^7.13.10", 20 | "@babel/plugin-syntax-class-properties": "^7.12.13", 21 | "@babel/plugin-transform-classes": "^7.13.0", 22 | "babel-traverse": "^6.26.0", 23 | "babel-types": "^6.26.0", 24 | "chalk": "^4.1.0", 25 | "css-tree": "^1.1.2", 26 | "md5": "^2.3.0" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.13.10", 30 | "@babel/preset-env": "^7.13.10", 31 | "@rollup/plugin-json": "^4.1.0", 32 | "rollup": "^2.41.4", 33 | "rollup-plugin-babel": "^4.4.0", 34 | "rollup-plugin-commonjs": "^10.1.0", 35 | "rollup-plugin-node-resolve": "^5.2.0", 36 | "rollup-plugin-typescript2": "^0.30.0", 37 | "tslib": "^2.2.0", 38 | "typescript": "^4.2.4", 39 | "webpack": "^5.35.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # react-css-module-plugin 2 | [![NPM](https://img.shields.io/npm/v/react-css-module-plugin.svg)](https://www.npmjs.com/package/lazylist-react) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 3 | ## Why write this plugin 4 | Because the process of using traditional cssModule is a bit cumbersome.You must first write the css class name in advance, and then import.And the class name can only be a word name or a camel case name, if you use "_" or "-" when specifying the class name, you need to write style['classname'] like this.So I have been thinking about whether there is an easier way to specify the scope of css. 5 | ## tips 6 | If you are accessing this plugin from an old system,It is best to use the `includes` attribute to support your new components.Avoid old style confusion.If it is a new system,Feel free to use it boldly.**global-className**can specify global style 7 | ## install 8 | ```bash 9 | npm i react-css-module-plugin --dev 10 | // or 11 | yarn add react-css-module-plugin --dev 12 | ``` 13 | ## Usage 14 | ```js 15 | // webpack.config.js 16 | const ReactCssModulePlugin = require("react-css-module-plugin"); 17 | module.exports = { 18 | // ...some config 19 | plugins:[ 20 | new ReactCssModulePlugin({ preStyle:'.less', preFile:["js","jsx"] }) 21 | ] 22 | } 23 | 24 | ``` 25 | ## Props 26 | * preStyle: .less、.scss、.sass or .css.**necessary**,Represents which style file you will deal with 27 | * preFile: ["js","jsx","ts","tsx"],Some or all of them,**necessary**,Represents what kind of react file to process. 28 | * includes: type Array,Which files are only processed. 29 | * excludes: type Array,Which files are not processed. 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import babel from 'rollup-plugin-babel' 3 | import commonjs from 'rollup-plugin-commonjs'; 4 | import typescript from 'rollup-plugin-typescript2' 5 | const resolveFile = function (filePath) { 6 | return path.join(__dirname, filePath); 7 | }; 8 | 9 | const plugins = [ 10 | typescript(), 11 | commonjs(), 12 | babel({ 13 | exclude: 'node_modules/**', 14 | babelrc: false, 15 | presets: [ 16 | ['@babel/preset-env', { modules: false }] 17 | ], 18 | plugins: [ 19 | '@babel/plugin-syntax-class-properties', 20 | ['@babel/plugin-transform-classes', { 21 | 'loose': true 22 | }] 23 | ] 24 | }), 25 | ]; 26 | 27 | export default [ 28 | { 29 | input: resolveFile('src/index.ts'), 30 | output: { 31 | file: resolveFile('lib/index.js'), 32 | format: 'cjs', 33 | exports: 'auto', 34 | }, 35 | plugins, 36 | }, 37 | { 38 | input: resolveFile('src/css-loader.ts'), 39 | context: '', 40 | output: { 41 | file: resolveFile('lib/css-loader.js'), 42 | format: 'cjs', 43 | exports: 'auto', 44 | }, 45 | plugins, 46 | 47 | }, 48 | { 49 | input: resolveFile('src/jsx-loader.ts'), 50 | output: { 51 | file: resolveFile('lib/jsx-loader.js'), 52 | format: 'cjs', 53 | exports: 'auto', 54 | }, 55 | plugins, 56 | }, 57 | { 58 | input: resolveFile('src/utils.ts'), 59 | output: { 60 | file: resolveFile('lib/utils.js'), 61 | format: 'cjs', 62 | exports: 'auto', 63 | }, 64 | plugins, 65 | }, 66 | ] 67 | -------------------------------------------------------------------------------- /src/jsx-loader.ts: -------------------------------------------------------------------------------- 1 | import { Options } from './@types'; 2 | const traverse = require("babel-traverse").default; 3 | const md5 = require('md5'); 4 | const babel = require("@babel/core"); 5 | const path = require('path'); 6 | const t = require("babel-types"); 7 | const parser = require("@babel/parser"); 8 | const loaderUtils = require('loader-utils'); 9 | const { styleType, defaultConfig } = require('./utils'); 10 | 11 | let _cache: any = {}; 12 | const fileEnd = styleType; 13 | 14 | function createHash(dep: string): string { 15 | if (_cache[dep]) return _cache[dep]; 16 | const hash: string = md5(dep).substr(0, 6); 17 | _cache[dep] = hash; 18 | return hash; 19 | }; 20 | 21 | function isSubPath(excludes: string | string[],context: string): boolean { 22 | if (typeof (excludes) === 'string') { 23 | return context.includes(excludes); 24 | } 25 | if (Array.isArray(excludes)) { 26 | return !!(excludes.find(p => context.includes(p))); 27 | } 28 | return false; 29 | } 30 | 31 | function selectFileName(_path: string): string { 32 | if (typeof (_path) !== 'string') throw ('the path is not string'); 33 | let { name, dir } = path.parse(_path); 34 | if (name === 'index') { 35 | name = dir.split(path.sep)[dir.split(path.sep).length - 1]; 36 | } 37 | return name; 38 | } 39 | 40 | module.exports = function(source: T): T { 41 | const self: any = this; 42 | const thisOptions: Options = loaderUtils.getOptions(this); 43 | const resourcePath: string = self.resourcePath; 44 | if (thisOptions.includes) { 45 | const y: boolean = isSubPath(thisOptions.includes, resourcePath); 46 | if (!y) return source; 47 | } 48 | if (thisOptions.excludes) { 49 | const y: boolean = isSubPath(thisOptions.excludes, resourcePath); 50 | if (y) return source; 51 | } 52 | const ast: import("@babel/types").File = parser.parse(source, defaultConfig); 53 | let canTraverse: boolean = false; 54 | traverse(ast, { 55 | ImportDeclaration: function (p: any) { 56 | const source = p.node.source; 57 | if (!t.isStringLiteral(source)) { 58 | return p.skip(); 59 | } 60 | const extname: string = path.extname(source.value); 61 | if (styleType.includes(extname)) { 62 | canTraverse = true; 63 | } 64 | } 65 | }); 66 | if (!canTraverse) return source; 67 | const preName = selectFileName(resourcePath); 68 | _cache = {}; 69 | const classHashChange: any = {}; 70 | const options = { 71 | JSXAttribute: function(path: any) { 72 | const { name, value } = path.node; 73 | if (name.name !== 'className') return; 74 | if (!t.isStringLiteral(value)) return; 75 | const classNames: string = value.value; 76 | const newClassNames: Set = new Set(); 77 | classNames.split(" ").map(className => { 78 | if (className.includes('global-')) { 79 | newClassNames.add(className) 80 | return; 81 | } 82 | const newClassName = `${preName}_${className}_${createHash(resourcePath + className)}`; 83 | classHashChange[className] = newClassName; 84 | newClassNames.add(newClassName); 85 | }) 86 | value.value = [...newClassNames].join(" "); 87 | return path.skip() 88 | }, 89 | 90 | } 91 | traverse(ast, options); 92 | traverse(ast, { 93 | StringLiteral: function StringLiteral(p: any) { 94 | const { value } = p.node; 95 | if (!fileEnd.includes(path.extname(value))) return p.skip(); 96 | p.node.value = `${value}?${JSON.stringify(classHashChange)}`; 97 | return p.skip(); 98 | }, 99 | 100 | }); 101 | 102 | const { code } = babel.transformFromAstSync(ast, null, { 103 | configFile: false 104 | }); 105 | return code; 106 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Compiler, RuleSetRule } from 'webpack'; 2 | import { ScopeJsxCssPluginI, Options, PreStyle, JsConfig } from './@types'; 3 | const path = require('path'); 4 | const chalk = require('chalk'); 5 | const { preFileTransFormReg, styleType, fileType, preMap } = require('./utils'); 6 | const SCOPE_JSX_CSS_PLUGIN = 'scope-jsx-css-plugin'; 7 | const log = console.log; 8 | class ScopeJsxCssPlugin implements ScopeJsxCssPluginI{ 9 | private options: Options; 10 | constructor(options: Options) { 11 | this.options = options; 12 | if (!options.preStyle) { 13 | throw Error('must have an type,such .less、.scss、.sass or .css') 14 | } 15 | if (typeof (options.preFile) === 'string') { 16 | if (!fileType.includes(options.preFile)) { 17 | throw Error('the preFile must one of [".js", ".jsx", ".tsx", ".ts"]') 18 | } 19 | } 20 | if (Array.isArray(options.preFile)) { 21 | if (options.preFile.length > 4) { 22 | log(chalk.red('it maybe has cannot resolve file')); 23 | } 24 | for (let i = 0; i < options.preFile.length; i++){ 25 | if (!fileType.includes(options.preFile[i])) { 26 | throw Error('the preFile must one of ["js", "jsx", "tsx", "ts"]') 27 | } 28 | } 29 | } 30 | } 31 | 32 | isReg(r:any): boolean { 33 | return r instanceof RegExp; 34 | } 35 | 36 | apply(compiler: Compiler): void { 37 | const self = this; 38 | const options: Options = this.options; 39 | if (!styleType.includes(options.preStyle)) { 40 | throw Error('the preStyle must one of [".less", ".scss", ".sass", ".css"]') 41 | } 42 | const pre: PreStyle = options.preStyle; 43 | const excludes = options.excludes; 44 | const includes = options.includes; 45 | const preFile = options.preFile || 'js'; 46 | 47 | compiler.hooks.afterPlugins.tap( 48 | SCOPE_JSX_CSS_PLUGIN, 49 | () => { 50 | let loaders: RuleSetRule[] | any = compiler.options.module.rules; 51 | let preLoader: RuleSetRule = loaders.find((evl: RuleSetRule) => { 52 | return evl.test instanceof RegExp && evl.test.test(pre); 53 | }); 54 | if (!preLoader) { 55 | const oneOf: RuleSetRule | any = compiler.options.module.rules.find((evl: any) => evl.oneOf && Array.isArray(evl.oneOf)); 56 | loaders = oneOf && oneOf.oneOf; 57 | if (Array.isArray(loaders)) { 58 | preLoader = loaders.find((item: any) => item.test && self.isReg(item.test) && item.test.test(pre)); 59 | } 60 | }; 61 | const l: string = preMap[pre]; 62 | if (preLoader && Array.isArray(preLoader.use)) { 63 | const index: number = preLoader.use.findIndex((item: any) => { 64 | if (typeof (item) === 'object') { 65 | return item.loader.includes(l); 66 | } 67 | if (typeof (item) === 'string') { 68 | return item.includes(l); 69 | } 70 | }); 71 | if (index > -1) { 72 | const copyUse = [...preLoader.use]; 73 | copyUse.splice(index, 0, { loader: path.join(__dirname, 'css-loader.js') }); 74 | preLoader.use = copyUse; 75 | } 76 | } 77 | const test: RegExp = preFileTransFormReg(preFile) 78 | const jsConfig: JsConfig = { 79 | test, 80 | exclude: /node_modules/, 81 | loader: path.join(__dirname, 'jsx-loader'), 82 | options: { 83 | excludes, 84 | includes, 85 | } 86 | }; 87 | compiler.options.module.rules.push(jsConfig); 88 | } 89 | ); 90 | } 91 | } 92 | module.exports = ScopeJsxCssPlugin; 93 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! ***************************************************************************** 4 | Copyright (c) Microsoft Corporation. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | ***************************************************************************** */ 17 | 18 | function __read(o, n) { 19 | var m = typeof Symbol === "function" && o[Symbol.iterator]; 20 | if (!m) return o; 21 | var i = m.call(o), r, ar = [], e; 22 | try { 23 | while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); 24 | } 25 | catch (error) { e = { error: error }; } 26 | finally { 27 | try { 28 | if (r && !r.done && (m = i["return"])) m.call(i); 29 | } 30 | finally { if (e) throw e.error; } 31 | } 32 | return ar; 33 | } 34 | 35 | function __spreadArray(to, from) { 36 | for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) 37 | to[j] = from[i]; 38 | return to; 39 | } 40 | 41 | var path = require('path'); 42 | var chalk = require('chalk'); 43 | var _a = require('./utils'), preFileTransFormReg = _a.preFileTransFormReg, styleType = _a.styleType, fileType = _a.fileType, preMap = _a.preMap; 44 | var SCOPE_JSX_CSS_PLUGIN = 'scope-jsx-css-plugin'; 45 | var log = console.log; 46 | var ScopeJsxCssPlugin = /** @class */ (function () { 47 | function ScopeJsxCssPlugin(options) { 48 | this.options = options; 49 | if (!options.preStyle) { 50 | throw Error('must have an type,such .less、.scss、.sass or .css'); 51 | } 52 | if (typeof (options.preFile) === 'string') { 53 | if (!fileType.includes(options.preFile)) { 54 | throw Error('the preFile must one of [".js", ".jsx", ".tsx", ".ts"]'); 55 | } 56 | } 57 | if (Array.isArray(options.preFile)) { 58 | if (options.preFile.length > 4) { 59 | log(chalk.red('it maybe has cannot resolve file')); 60 | } 61 | for (var i = 0; i < options.preFile.length; i++) { 62 | if (!fileType.includes(options.preFile[i])) { 63 | throw Error('the preFile must one of ["js", "jsx", "tsx", "ts"]'); 64 | } 65 | } 66 | } 67 | } 68 | ScopeJsxCssPlugin.prototype.isReg = function (r) { 69 | return r instanceof RegExp; 70 | }; 71 | ScopeJsxCssPlugin.prototype.apply = function (compiler) { 72 | var self = this; 73 | var options = this.options; 74 | if (!styleType.includes(options.preStyle)) { 75 | throw Error('the preStyle must one of [".less", ".scss", ".sass", ".css"]'); 76 | } 77 | var pre = options.preStyle; 78 | var excludes = options.excludes; 79 | var includes = options.includes; 80 | var preFile = options.preFile || 'js'; 81 | compiler.hooks.afterPlugins.tap(SCOPE_JSX_CSS_PLUGIN, function () { 82 | var loaders = compiler.options.module.rules; 83 | var preLoader = loaders.find(function (evl) { 84 | return evl.test instanceof RegExp && evl.test.test(pre); 85 | }); 86 | if (!preLoader) { 87 | var oneOf = compiler.options.module.rules.find(function (evl) { return evl.oneOf && Array.isArray(evl.oneOf); }); 88 | loaders = oneOf && oneOf.oneOf; 89 | if (Array.isArray(loaders)) { 90 | preLoader = loaders.find(function (item) { return item.test && self.isReg(item.test) && item.test.test(pre); }); 91 | } 92 | } 93 | var l = preMap[pre]; 94 | if (preLoader && Array.isArray(preLoader.use)) { 95 | var index = preLoader.use.findIndex(function (item) { 96 | if (typeof (item) === 'object') { 97 | return item.loader.includes(l); 98 | } 99 | if (typeof (item) === 'string') { 100 | return item.includes(l); 101 | } 102 | }); 103 | if (index > -1) { 104 | var copyUse = __spreadArray([], __read(preLoader.use)); 105 | copyUse.splice(index, 0, { loader: path.join(__dirname, 'css-loader.js') }); 106 | preLoader.use = copyUse; 107 | } 108 | } 109 | var test = preFileTransFormReg(preFile); 110 | var jsConfig = { 111 | test: test, 112 | exclude: /node_modules/, 113 | loader: path.join(__dirname, 'jsx-loader'), 114 | options: { 115 | excludes: excludes, 116 | includes: includes, 117 | } 118 | }; 119 | compiler.options.module.rules.push(jsConfig); 120 | }); 121 | }; 122 | return ScopeJsxCssPlugin; 123 | }()); 124 | module.exports = ScopeJsxCssPlugin; 125 | -------------------------------------------------------------------------------- /lib/jsx-loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*! ***************************************************************************** 4 | Copyright (c) Microsoft Corporation. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | ***************************************************************************** */ 17 | 18 | function __read(o, n) { 19 | var m = typeof Symbol === "function" && o[Symbol.iterator]; 20 | if (!m) return o; 21 | var i = m.call(o), r, ar = [], e; 22 | try { 23 | while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); 24 | } 25 | catch (error) { e = { error: error }; } 26 | finally { 27 | try { 28 | if (r && !r.done && (m = i["return"])) m.call(i); 29 | } 30 | finally { if (e) throw e.error; } 31 | } 32 | return ar; 33 | } 34 | 35 | function __spreadArray(to, from) { 36 | for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) 37 | to[j] = from[i]; 38 | return to; 39 | } 40 | 41 | var traverse = require("babel-traverse").default; 42 | var md5 = require('md5'); 43 | var babel = require("@babel/core"); 44 | var path = require('path'); 45 | var t = require("babel-types"); 46 | var parser = require("@babel/parser"); 47 | var loaderUtils = require('loader-utils'); 48 | var _a = require('./utils'), styleType = _a.styleType, defaultConfig = _a.defaultConfig; 49 | var _cache = {}; 50 | var fileEnd = styleType; 51 | function createHash(dep) { 52 | if (_cache[dep]) 53 | return _cache[dep]; 54 | var hash = md5(dep).substr(0, 6); 55 | _cache[dep] = hash; 56 | return hash; 57 | } 58 | function isSubPath(excludes, context) { 59 | if (typeof (excludes) === 'string') { 60 | return context.includes(excludes); 61 | } 62 | if (Array.isArray(excludes)) { 63 | return !!(excludes.find(function (p) { return context.includes(p); })); 64 | } 65 | return false; 66 | } 67 | function selectFileName(_path) { 68 | if (typeof (_path) !== 'string') 69 | throw ('the path is not string'); 70 | var _a = path.parse(_path), name = _a.name, dir = _a.dir; 71 | if (name === 'index') { 72 | name = dir.split(path.sep)[dir.split(path.sep).length - 1]; 73 | } 74 | return name; 75 | } 76 | module.exports = function (source) { 77 | var self = this; 78 | var thisOptions = loaderUtils.getOptions(this); 79 | var resourcePath = self.resourcePath; 80 | if (thisOptions.includes) { 81 | var y = isSubPath(thisOptions.includes, resourcePath); 82 | if (!y) 83 | return source; 84 | } 85 | if (thisOptions.excludes) { 86 | var y = isSubPath(thisOptions.excludes, resourcePath); 87 | if (y) 88 | return source; 89 | } 90 | var ast = parser.parse(source, defaultConfig); 91 | var canTraverse = false; 92 | traverse(ast, { 93 | ImportDeclaration: function (p) { 94 | var source = p.node.source; 95 | if (!t.isStringLiteral(source)) { 96 | return p.skip(); 97 | } 98 | var extname = path.extname(source.value); 99 | if (styleType.includes(extname)) { 100 | canTraverse = true; 101 | } 102 | } 103 | }); 104 | if (!canTraverse) 105 | return source; 106 | var preName = selectFileName(resourcePath); 107 | _cache = {}; 108 | var classHashChange = {}; 109 | var options = { 110 | JSXAttribute: function (path) { 111 | var _a = path.node, name = _a.name, value = _a.value; 112 | if (name.name !== 'className') 113 | return; 114 | if (!t.isStringLiteral(value)) 115 | return; 116 | var classNames = value.value; 117 | var newClassNames = new Set(); 118 | classNames.split(" ").map(function (className) { 119 | if (className.includes('global-')) { 120 | newClassNames.add(className); 121 | return; 122 | } 123 | var newClassName = preName + "_" + className + "_" + createHash(resourcePath + className); 124 | classHashChange[className] = newClassName; 125 | newClassNames.add(newClassName); 126 | }); 127 | value.value = __spreadArray([], __read(newClassNames)).join(" "); 128 | return path.skip(); 129 | }, 130 | }; 131 | traverse(ast, options); 132 | traverse(ast, { 133 | StringLiteral: function StringLiteral(p) { 134 | var value = p.node.value; 135 | if (!fileEnd.includes(path.extname(value))) 136 | return p.skip(); 137 | p.node.value = value + "?" + JSON.stringify(classHashChange); 138 | return p.skip(); 139 | }, 140 | }); 141 | var code = babel.transformFromAstSync(ast, null, { 142 | configFile: false 143 | }).code; 144 | return code; 145 | }; 146 | --------------------------------------------------------------------------------