diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5f79528 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# editorconfig.org + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..a90d9cf --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +/coverage +/dist +/node_modules +/test/fixtures diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..29e7717 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ["@webpack-contrib/eslint-config-webpack", "prettier"], +}; diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..39f4a79 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +package-lock.json -diff +* text=auto +bin/* eol=lf +yarn.lock -diff diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd13c07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +logs +*.log +npm-debug.log* +.eslintcache + +/coverage +/dist +/local +/reports +/node_modules +index.js + +.DS_Store +Thumbs.db +.idea +*.iml +.vscode +*.sublime-project +*.sublime-workspace +.nyc_output +test/outputs diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..618abcb --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +/coverage +/dist +/node_modules +/test/fixtures +CHANGELOG.md +/test/sass +/test/scss diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d5fa73 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright JS Foundation and other contributors + +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: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6dfcf4b --- /dev/null +++ b/README.md @@ -0,0 +1,184 @@ +# @zougt/vite-plugin-theme-preprocessor + +一个[vite v2.0+](https://cn.vitejs.dev/)插件,用于实现多个 `less`、`sass` 变量文件编译出多主题的 css + +使得基于`less`、`sass`以及`css modules`的主题样式在线动态切换变得很简单 + +使用了插件钩子: + +- config +- configResolved +- buildStart +- generateBundle +- transformIndexHtml + +> 注:由于 vite 内置 css 插件未提供外接`less`、`sass`的口子(类似[`webpack-contrib/less-loader`](https://github.com/webpack-contrib/less-loader)的`implementation`),在`@zougt/vite-plugin-theme-preprocessor`的 buildStart 内替换了相对于根目录的 node_modules 里面的`less`或`sass` + +## 安装与使用 + +```bash +# use npm +npm install @zougt/vite-plugin-theme-preprocessor -D +# use yarn +yarn add @zougt/vite-plugin-theme-preprocessor -D +``` + +**vite.config.js** + +```js +import themePreprocessorPlugin, { + getModulesScopeGenerater, +} from "@zougt/vite-plugin-theme-preprocessor"; +export default { + plugins: [ + themePreprocessorPlugin({ + scss: { + multipleScopeVars: [ + { + scopeName: "theme-default", + path: path.resolve("src/theme/default-vars.scss"), + }, + { + scopeName: "theme-mauve", + path: path.resolve("src/theme/mauve-vars.scss"), + }, + ], + // 默认取 multipleScopeVars[0].scopeName + defaultScopeName: "", + // 在生产模式是否抽取独立的主题css文件,extract为true以下属性有效 + extract: true, + // 独立主题css文件的输出路径,默认取 viteConfig.build.assetsDir 相对于 (viteConfig.build.outDir) + outputDir: "", + // 会选取defaultScopeName对应的主题css文件在html添加link + themeLinkTagId: "theme-link-tag", + // "head"||"head-prepend" || "body" ||"body-prepend" + themeLinkTagInjectTo: "head", + // 是否对抽取的css文件内对应scopeName的权重类名移除 + removeCssScopeName: false, + // 可以自定义css文件名称的函数 + customThemeCssFileName: (scopeName) => scopeName, + }, + // less: { + // multipleScopeVars: [ + // { + // scopeName: "theme-default", + // path: path.resolve("src/theme/default-vars.less"), + // }, + // { + // scopeName: "theme-mauve", + // path: path.resolve("src/theme/mauve-vars.less"), + // }, + // ], + // }, + }), + ], +}; +``` + +## 多主题编译示例(以 sass 为例) + +```scss +//src/theme/default-vars.scss +/** +*此scss变量文件作为multipleScopeVars去编译时,会自动移除!default以达到变量提升 +*同时此scss变量文件作为默认主题变量文件,被其他.scss通过 @import 时,必需 !default +*/ +$primary-color: #0081ff !default; +``` + +```scss +//src/theme/mauve-vars.scss +$primary-color: #9c26b0; +``` + +```scss +//src/components/Button/style.scss +@import "../../theme/default-vars"; +.un-btn { + position: relative; + display: inline-block; + font-weight: 400; + white-space: nowrap; + text-align: center; + border: 1px solid transparent; + background-color: $primary-color; + .anticon { + line-height: 1; + } +} +``` + +编译之后 + +src/components/Button/style.css + +```css +.un-btn { + position: relative; + display: inline-block; + font-weight: 400; + white-space: nowrap; + text-align: center; + border: 1px solid transparent; +} +.theme-default .un-btn { + background-color: #0081ff; +} +.theme-mauve .un-btn { + background-color: #9c26b0; +} +.un-btn .anticon { + line-height: 1; +} +``` + +### 并且支持 Css Modules + +对于`*.module.scss`,得到的 css 类似: + +```css +.src-components-Button-style_un-btn-1n85E { + position: relative; + display: inline-block; + font-weight: 400; + white-space: nowrap; + text-align: center; + border: 1px solid transparent; +} +.theme-default .src-components-Button-style_un-btn-1n85E { + background-color: #0081ff; +} +.theme-mauve .src-components-Button-style_un-btn-1n85E { + background-color: #9c26b0; +} +.src-components-Button-style_un-btn-1n85E + .src-components-Button-style_anticon-1n85E { + line-height: 1; +} +``` + +## 在线切换主题 css 文件 + +```js +const toggleTheme = (scopeName = "theme-default") => { + let styleLink = document.getElementById("theme-link-tag"); + if (styleLink) { + // 假如存在id为theme-link-tag 的link标签,直接修改其href + styleLink.href = `/${scopeName}.css`; + // 注:如果是removeCssScopeName:true移除了主题文件的权重类名,就可以不用修改className 操作 + document.documentElement.className = scopeName; + } else { + // 不存在的话,则新建一个 + styleLink = document.createElement("link"); + styleLink.type = "text/css"; + styleLink.rel = "stylesheet"; + styleLink.id = "theme-link-tag"; + styleLink.href = `/${scopeName}.css`; + // 注:如果是removeCssScopeName:true移除了主题文件的权重类名,就可以不用修改className 操作 + document.documentElement.className = scopeName; + document.head.append(styleLink); + } +}; +``` + +webpack 版本的实现方案请查看[`@zougt/some-loader-utils`](https://github.com/GitOfZGT/some-loader-utils#getSass) diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..e937a52 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,19 @@ +const MIN_BABEL_VERSION = 7; + +module.exports = (api) => { + api.assertVersion(MIN_BABEL_VERSION); + api.cache(true); + + return { + presets: [ + [ + "@babel/preset-env", + { + targets: { + node: "12.0.0", + }, + }, + ], + ], + }; +}; diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..69b4242 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ["@commitlint/config-conventional"], +}; diff --git a/husky.config.js b/husky.config.js new file mode 100644 index 0000000..6cf9b3f --- /dev/null +++ b/husky.config.js @@ -0,0 +1,6 @@ +module.exports = { + hooks: { + "pre-commit": "lint-staged", + "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", + }, +}; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..5da099d --- /dev/null +++ b/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + testEnvironment: "node", +}; diff --git a/lint-staged.config.js b/lint-staged.config.js new file mode 100644 index 0000000..dc1bf51 --- /dev/null +++ b/lint-staged.config.js @@ -0,0 +1,4 @@ +module.exports = { + "*.js": ["eslint --fix", "prettier --write"], + "*.{json,md,yml,css,ts}": ["prettier --write"], +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..8094a18 --- /dev/null +++ b/package.json @@ -0,0 +1,76 @@ +{ + "name": "@zougt/vite-plugin-theme-preprocessor", + "version": "1.0.0", + "description": "css theme preprocessor plugin for vite", + "license": "MIT", + "repository": "GitOfZGT/vite-plugin-theme-preprocessor", + "author": "zougt", + "homepage": "https://github.com/GitOfZGT/vite-plugin-theme-preprocessor", + "bugs": "https://github.com/GitOfZGT/vite-plugin-theme-preprocessor/issues", + "main": "dist/index.js", + "engines": { + "node": ">= 12.0.0" + }, + "scripts": { + "start": "npm run build -- -w", + "clean": "del-cli dist", + "prebuild": "npm run clean", + "build": "cross-env NODE_ENV=production babel src -d dist --copy-files", + "commitlint": "commitlint --from=master", + "security": "npm audit", + "lint:prettier": "prettier --list-different .", + "lint:js": "eslint --cache .", + "lint": "npm-run-all -l -p \"lint:**\"", + "pretest": "npm run lint", + "prepare": "npm run build", + "release": "standard-version" + }, + "files": [ + "dist" + ], + "dependencies": { + "@zougt/some-loader-utils": "^1.1.0", + "fs-extra": "^9.1.0", + "string-hash": "^1.1.3" + }, + "devDependencies": { + "@babel/cli": "^7.12.10", + "@babel/core": "^7.12.10", + "@babel/preset-env": "^7.12.11", + "@commitlint/cli": "^11.0.0", + "@commitlint/config-conventional": "^11.0.0", + "@webpack-contrib/defaults": "^6.3.0", + "@webpack-contrib/eslint-config-webpack": "^3.0.0", + "babel-jest": "^26.6.3", + "cross-env": "^7.0.3", + "del": "^6.0.0", + "del-cli": "^3.0.1", + "enhanced-resolve": "^5.5.0", + "eslint": "^7.13.0", + "eslint-config-prettier": "^7.1.0", + "eslint-plugin-import": "^2.22.1", + "foundation-sites": "^6.6.3", + "husky": "^4.3.6", + "jest": "^26.6.3", + "less": "^4.1.1", + "lint-staged": "^10.5.4", + "npm-run-all": "^4.1.5", + "prettier": "^2.2.1", + "sass": "^1.32.8", + "semver": "^7.3.4", + "standard-version": "^9.1.0", + "stylus": "^0.54.8" + }, + "keywords": [ + "vite-plugin", + "theme", + "css", + "less", + "sass", + "stylus", + "preprocessor" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org" + } +} diff --git a/src/substitute/less/bin/lessc b/src/substitute/less/bin/lessc new file mode 100644 index 0000000..a09a86d --- /dev/null +++ b/src/substitute/less/bin/lessc @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +// eslint-disable-next-line import/no-unresolved +require("@zougt/vite-plugin-theme-preprocessor/original/less/bin/lessc"); diff --git a/src/substitute/less/dist/less.js b/src/substitute/less/dist/less.js new file mode 100644 index 0000000..cafc7cd --- /dev/null +++ b/src/substitute/less/dist/less.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/no-unresolved +export * from "@zougt/vite-plugin-theme-preprocessor/original/less/dist/less"; diff --git a/src/substitute/less/dist/less.min.js b/src/substitute/less/dist/less.min.js new file mode 100644 index 0000000..0a1e558 --- /dev/null +++ b/src/substitute/less/dist/less.min.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/no-unresolved +export * from "@zougt/vite-plugin-theme-preprocessor/original/less/dist/less.min"; diff --git a/src/substitute/less/package.json b/src/substitute/less/package.json new file mode 100644 index 0000000..d9f58f7 --- /dev/null +++ b/src/substitute/less/package.json @@ -0,0 +1,136 @@ +{ + "name": "less", + "version": "4.1.1", + "description": "Leaner CSS", + "homepage": "http://lesscss.org", + "author": { + "name": "Alexis Sellier", + "email": "self@cloudhead.net" + }, + "contributors": [ + "The Core Less Team" + ], + "bugs": { + "url": "https://github.com/less/less.js/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/less/less.js.git" + }, + "master": { + "url": "https://github.com/less/less.js/blob/master/", + "raw": "https://raw.githubusercontent.com/less/less.js/master/" + }, + "license": "Apache-2.0", + "bin": { + "lessc": "./bin/lessc" + }, + "main": "index", + "module": "./lib/less-node/index", + "directories": { + "test": "./test" + }, + "browser": "./dist/less.js", + "engines": { + "node": ">=6" + }, + "scripts": { + "test": "grunt test", + "grunt": "grunt", + "build": "npm-run-all clean compile", + "clean": "shx rm -rf ./lib tsconfig.tsbuildinfo", + "compile": "tsc -p tsconfig.json", + "copy:root": "shx cp -rf ./dist ../../", + "dev": "tsc -p tsconfig.json -w", + "prepublishOnly": "grunt dist" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^2.5.2", + "source-map": "~0.6.0" + }, + "devDependencies": { + "@less/test-data": "^4.1.0", + "@less/test-import-module": "^4.0.0", + "@rollup/plugin-commonjs": "^17.0.0", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^11.0.0", + "@typescript-eslint/eslint-plugin": "^3.3.0", + "@typescript-eslint/parser": "^3.3.0", + "benny": "^3.6.12", + "bootstrap-less-port": "0.3.0", + "chai": "^4.2.0", + "diff": "^3.2.0", + "eslint": "^6.8.0", + "fs-extra": "^8.1.0", + "git-rev": "^0.2.1", + "globby": "^10.0.1", + "grunt": "^1.0.4", + "grunt-cli": "^1.3.2", + "grunt-contrib-clean": "^1.0.0", + "grunt-contrib-connect": "^1.0.2", + "grunt-eslint": "^21.1.0", + "grunt-saucelabs": "^9.0.1", + "grunt-shell": "^1.3.0", + "html-template-tag": "^3.2.0", + "jit-grunt": "^0.10.0", + "less-plugin-autoprefix": "^1.5.1", + "less-plugin-clean-css": "^1.5.1", + "minimist": "^1.2.0", + "mocha": "^6.2.1", + "mocha-headless-chrome": "^2.0.3", + "mocha-teamcity-reporter": "^3.0.0", + "nock": "^11.8.2", + "npm-run-all": "^4.1.5", + "performance-now": "^0.2.0", + "phin": "^2.2.3", + "promise": "^7.1.1", + "read-glob": "^3.0.0", + "resolve": "^1.17.0", + "rollup": "^2.34.1", + "rollup-plugin-terser": "^5.1.1", + "rollup-plugin-typescript2": "^0.29.0", + "semver": "^6.3.0", + "shx": "^0.3.2", + "time-grunt": "^1.3.0", + "ts-node": "^8.4.1", + "typescript": "^4.1.3", + "uikit": "2.27.4" + }, + "keywords": [ + "compile less", + "css nesting", + "css variable", + "css", + "gradients css", + "gradients css3", + "less compiler", + "less css", + "less mixins", + "less", + "less.js", + "lesscss", + "mixins", + "nested css", + "parser", + "preprocessor", + "bootstrap css", + "bootstrap less", + "style", + "styles", + "stylesheet", + "variables in css", + "css less" + ], + "rawcurrent": "https://raw.github.com/less/less.js/v", + "sourcearchive": "https://github.com/less/less.js/archive/v", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^1.10.0" + } +} diff --git a/src/substitute/less/preprocessor-substitute-options.js b/src/substitute/less/preprocessor-substitute-options.js new file mode 100644 index 0000000..e364eb1 --- /dev/null +++ b/src/substitute/less/preprocessor-substitute-options.js @@ -0,0 +1 @@ +/** 作为替换了处理器的标识文件,必须存在*/ diff --git a/src/substitute/sass/package.json b/src/substitute/sass/package.json new file mode 100644 index 0000000..91c9e18 --- /dev/null +++ b/src/substitute/sass/package.json @@ -0,0 +1,34 @@ +{ + "name": "sass", + "description": "A pure JavaScript implementation of Sass.", + "license": "MIT", + "bugs": "https://github.com/sass/dart-sass/issues", + "homepage": "https://github.com/sass/dart-sass", + "repository": { + "type": "git", + "url": "https://github.com/sass/dart-sass" + }, + "author": { + "name": "Natalie Weizenbaum", + "email": "nweiz@google.com", + "url": "https://github.com/nex3" + }, + "engines": { + "node": ">=8.9.0" + }, + "dependencies": { + "chokidar": ">=2.0.0 <4.0.0" + }, + "keywords": [ + "style", + "scss", + "sass", + "preprocessor", + "css" + ], + "version": "1.32.8", + "bin": { + "sass": "sass.js" + }, + "main": "sass.dart.js" +} diff --git a/src/substitute/sass/preprocessor-substitute-options.js b/src/substitute/sass/preprocessor-substitute-options.js new file mode 100644 index 0000000..e364eb1 --- /dev/null +++ b/src/substitute/sass/preprocessor-substitute-options.js @@ -0,0 +1 @@ +/** 作为替换了处理器的标识文件,必须存在*/ diff --git a/src/substitute/sass/sass-render.js b/src/substitute/sass/sass-render.js new file mode 100644 index 0000000..0b2d2a9 --- /dev/null +++ b/src/substitute/sass/sass-render.js @@ -0,0 +1,12 @@ +/* eslint-disable import/no-unresolved */ +/* eslint-disable import/extensions */ +/* eslint-disable import/no-dynamic-require */ +/* eslint-disable global-require */ + +import sass from "@zougt/vite-plugin-theme-preprocessor/original/sass/sass.dart"; + +import { getSass } from "@zougt/some-loader-utils"; + +export default getSass({ + implementation: sass, +}); diff --git a/src/substitute/sass/sass.dart.js b/src/substitute/sass/sass.dart.js new file mode 100644 index 0000000..42bc614 --- /dev/null +++ b/src/substitute/sass/sass.dart.js @@ -0,0 +1 @@ +module.exports = require("./sass-render").default; diff --git a/src/substitute/sass/sass.js b/src/substitute/sass/sass.js new file mode 100644 index 0000000..969fd8a --- /dev/null +++ b/src/substitute/sass/sass.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +var module = require('./sass.dart.js'); +module.cli_pkg_main_0_(process.argv.slice(2));