{"id":23093800,"url":"https://github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo","last_synced_at":"2025-08-16T10:32:11.187Z","repository":{"id":37663452,"uuid":"130295051","full_name":"hiyangguo/webpack-mutiple-theme-bundle-css-demo","owner":"hiyangguo","description":"🎀A webpack mutiple theme bundle css demo.","archived":false,"fork":false,"pushed_at":"2018-11-04T14:21:05.000Z","size":838,"stargazers_count":36,"open_issues_count":3,"forks_count":25,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-03-05T14:27:00.944Z","etag":null,"topics":["less","mutiple-theme","webpack"],"latest_commit_sha":null,"homepage":"http://hiyangguo.com/webpack-mutiple-theme-bundle-css-demo/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hiyangguo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-20T02:03:15.000Z","updated_at":"2022-12-21T07:33:22.000Z","dependencies_parsed_at":"2022-09-08T10:50:25.147Z","dependency_job_id":null,"html_url":"https://github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiyangguo%2Fwebpack-mutiple-theme-bundle-css-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiyangguo%2Fwebpack-mutiple-theme-bundle-css-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiyangguo%2Fwebpack-mutiple-theme-bundle-css-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiyangguo%2Fwebpack-mutiple-theme-bundle-css-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hiyangguo","download_url":"https://codeload.github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230032536,"owners_count":18162488,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["less","mutiple-theme","webpack"],"created_at":"2024-12-16T21:54:04.883Z","updated_at":"2024-12-16T21:54:05.694Z","avatar_url":"https://github.com/hiyangguo.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# webpack-mutiple-theme-bundle-css-demo\n本文主要详细介绍了，如何使用 [webpack][webpack]，打包多套不同主题的解决方案以及实践中所遇到的问题及解决方案。\n\n\u003e 如果你只是想快速编译多套主题，请直接使用 [webpack-multiple-themes-compile][webpack-multiple-themes-compile] 库。\n\u003c!-- more --\u003e\n\n## 起因\n首先，简单的介绍一下什么是多主题，所谓多套主题/配色，就是我们很常见的换肤功能。换肤简单来说就是更换 `css`。这是一个老生常谈的问题，具体实践请参考[less换肤功能实践][less-multiple-color-theme-realize]。本文不在赘述。\n一般实现多主题的样式文件，我们都会借用 [gulp][gulp]、[grunt][grunt]这种构建工具进行构建。但是，这样做有一个巨大的问题，就是非常不方便。我们既然已经使用了 `webpack` 进行打包，又为什么还要使用其他的构建工具呢？\n另外，还有一个巨大的弊端就是使用其他构建工具构建的 css ，是没办法支持提供的 [scope][scope]功能的。这非常致命。所以到底该如何使用 webpack 进行构建呢？\n\n## 大致思路\n新建一些 `\u003ctheme\u003e.less`文件，，使用 `webpack` 读取 `themes`目录中的样式文件，编译后输出 `\u003ctheme\u003e.css`。并且首次加载时只引用默认主题文件，其他的可以到切换的时候再引入。\n所以只需要解决解决编译多套 css 输出的问题和不让 css 注入 html的问题就好了。\n\n## 解决编译多套 css 输出的问题\n- 建立一个[初始化的项目][initial-project]，这个项目以`react`项目为例，预编译语言使用的是`less`。你可以随着自己的喜好进行任意选择。[初始配置][init-webpack-config]。然后再`less`文件夹下，新建一个`themes`目录，和多个 `\u003ctheme\u003e.less`。\n![目录结构](/static/themes.png)\n建好之后，把所有的 文件引入 `index.js`中，`webpack`就会帮你把他们编译输出到一起了。一般情况下，[extract-text-webpack-plugin][extract-text-webpack-plugin] 可以帮我们把样式文件抽出来，但是会帮我们把他们都放在同一个文件中。\n修改`index.js`。\n```diff\nimport './less/index.less';\n+ import './less/themes/green.less';\n+ import './less/themes/red.less';\n+ import './less/themes/yellow.less';\n```\n然后编译一下，你发现所有的样式都混在一起了。\n![混在一起的样式](/static/mix-css.png)\n参照文档，我们需要多次声明 `ExtractTextPlugin`，以达到把不同的主题输出到不同文件的目的。这里我使用的是, `loader` 的 `include` 和 `exclude`参数。在默认样式中将其他样式排除，然后每一个主题的样式,分别打包自己的样式。\n最终代码的改动如下：\n```diff\nconst path = require('path');\n+ const fs = require('fs');\nconst webpack = require('webpack');\nconst ExtractTextPlugin = require('extract-text-webpack-plugin');\nconst HtmlwebpackPlugin = require('html-webpack-plugin');\n\nconst { STYLE_DEBUG } = process.env;\n+ // 主题路径\n+ const THEME_PATH = './src/less/themes';\n\nconst extractLess = new ExtractTextPlugin('style.[hash].css');\n\n+ const styleLoaders = [{ loader: 'css-loader' }, { loader: 'less-loader' }];\n\n+ const resolveToThemeStaticPath = fileName =\u003e path.resolve(THEME_PATH, fileName);\n+ const themeFileNameSet = fs.readdirSync(path.resolve(THEME_PATH));\n+ const themePaths = themeFileNameSet.map(resolveToThemeStaticPath);\n+ const getThemeName = fileName =\u003e `theme-${path.basename(fileName, path.extname(fileName))}`;\n\n+ // 全部 ExtractLessS 的集合\n+ const themesExtractLessSet = themeFileNameSet.map(fileName =\u003e new ExtractTextPlugin(`${getThemeName(fileName)}.css`))\n+ // 主题 Loader 的集合\n+ const themeLoaderSet = themeFileNameSet.map((fileName, index) =\u003e {\n+   return {\n+     test: /\\.(less|css)$/,\n+     include: resolveToThemeStaticPath(fileName),\n+     loader: themesExtractLessSet[index].extract({\n+       use: styleLoaders\n+     })\n+   }\n+ });\n\n\n//\n//..... 这里省略了\n//\n\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: [\n          'transform-loader?brfs', // Use browserify transforms as webpack-loader.\n          'babel-loader?babelrc'\n        ],\n        exclude: /node_modules/\n      },\n      {\n        test: /\\.(less|css)$/,\n        exclude: themePaths,\n        loader: extractLess.extract({\n-          use: [\n-            {\n-              loader: 'css-loader',\n-            }, {\n-              loader: 'less-loader'\n-            }\n-          ],  \n+          use: styleLoaders,\n          // use style-loader in development\n          fallback: 'style-loader?{attrs:{prop: \"value\"}}'\n        })\n      },\n      {\n        test: /\\.html$/,\n        use: [\n          {\n            loader: 'html-loader'\n          }\n        ]\n      },\n+      ...themeLoaderSet\n    ]\n  },\n  plugins: [\n    extractLess,\n+    ...themesExtractLessSet,\n    new webpack.NamedModulesPlugin(),\n    new HtmlwebpackPlugin({\n      title: 'webpack 多主题打包演示',\n      template: 'src/index.html',\n      inject: true\n    })\n  ],\n  devtool: STYLE_DEBUG === 'SOURCE' \u0026\u0026 'source-map'\n};\n```\n\n做出以上改动之后，就可以正常的输出样式文件了。\n![第一次构建](/static/first-build.png)\n\n[详细的代码改动][git-commit-extract-css]在这里，并且有详细的注释。\n\n## 不让 css 注入 html\n这样做之后，虽然 `webpack` 可以正常的编译样式文件了，但是有一个致命的问题。让我们看看现在的`\u003chead/\u003e`\n```html\n\u003chead\u003e\n  \u003cmeta charset=\"UTF-8\" \u003e\n  \u003ctitle\u003ewebpack 多主题打包演示页面\u003c/title\u003e\n  \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" \u003e\n  \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u003e\n  \u003clink rel=\"stylesheet\" type=\"text/css\" href=\"/resources/loading.css\" \u003e\n  \u003clink rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css\" /\u003e\n  \u003cscript type=\"text/javascript\" src=\"//cdn.staticfile.org/babel-standalone/6.24.0/babel.min.js\"\u003e\u003c/script\u003e\n  \u003cscript src=\"https://cdn.polyfill.io/v2/polyfill.min.js?features=default|gated,Array.prototype.includes\"\u003e\u003c/script\u003e\n\u003clink href=\"/style.984c33e9f2d50d6db720.css\" rel=\"stylesheet\"\u003e\u003clink href=\"/theme-green.css\" rel=\"stylesheet\"\u003e\u003clink href=\"/theme-red.css\" rel=\"stylesheet\"\u003e\u003clink href=\"/theme-yellow.css\" rel=\"stylesheet\"\u003e\u003c/head\u003e\n```\n我们发现不仅注入了`style.css`同时注入了所有的`theme.css`。这显然不是我们想要的。所以有什么办法把多余的主题去掉呢？\n### 方法一（不推荐）\n用`node`写一个脚本，读取`html`，然后移除。这样又与我们最开始的初衷相违背，还是借助了其他的外力。\n### 方法二\n[extract-text-webpack-plugin][extract-text-webpack-plugin] 提供了一个 `excludeChunks`方法，可以用来排除 `entry` 。所以我们可以把所有的样式文件放入，`themes.js` 中然后 在 entry 中添加 `themes`。再使用`excludeChunks`排除它就好了。\n- 删除 `index.js` 中的样式引用。\n```diff\n// style\nimport './less/index.less';\n- import './less/themes/green.less';\n- import './less/themes/red.less';\n- import './less/themes/yellow.less';\n```\n- 创建`themes.js`\n```javascript\nimport './less/themes/green.less';\nimport './less/themes/red.less';\nimport './less/themes/yellow.less';\n```\n- 修改 `webpack.config.js`\n```diff\n  entry: {\n    app: './src/index.js',\n+   themes: './src/themes.js'\n  },\n//\n//... 省略没用的代码\n//\n\nnew HtmlwebpackPlugin({\n  title: 'webpack 多主题打包演示',\n  template: 'src/index.html',\n  inject: true,\n+ excludeChunks: ['themes']\n})\n```\n![使用 excludeChunks方式构建](/static/first-build.png)\n但是这时候，发现多了一个 `themes.bundle.js`文件。所以需要删除掉。修改 `build`脚本。\n```diff\n\"build\": \"rm -rf dist \u0026\u0026 NODE_ENV=production webpack --mode production --progress \u0026\u0026 cp -R public/* ./dist/ \"\n\"build\": \"rm -rf dist \u0026\u0026 NODE_ENV=production webpack --mode production --progress \u0026\u0026 cp -R public/* ./dist/ \u0026\u0026 \u0026\u0026 rm -rf dist/themes.bundle.js\"\n```\n这样就大功告成了。[更改记录][exclude-css-file]，[完整代码](https://github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo)\n\n### 方法三\n但是，加了句 `rm -rf`，还是感觉有点不爽。所以在仔细的阅读了[extract-text-webpack-plugin][extract-text-webpack-plugin]文档后，我发现他提供了一个钩子函数`html-webpack-plugin-after-html-processing`。可以处理`html`。[HtmlWebpackHandleCssInjectPlugin.js](html-webpack-handle-css-inject-plugin-js)（**支持`webpack4`和其他 `webpack` 版本**）。\n然后这样使用：\n```diff\n+ const HtmlWebpackHandleCssInjectPlugin = require('./HtmlWebpackHandleCssInjectPlugin');\n//... 省略没用的代码\n  plugins: [\n    extractLess,\n    // 将所有的 themesExtractLess 加入 plugin\n    ...themesExtractLessSet,\n    new webpack.NamedModulesPlugin(),\n    new HtmlwebpackPlugin({\n      title: 'webpack 多主题打包演示',\n      template: 'src/index.html',\n      inject: true\n+    }),\n+    new HtmlWebpackHandleCssInjectPlugin({\n+      filter: (filePath) =\u003e {\n+        return filePath.includes('style');\n+      }\n+    })\n+  ],\n```\n`filter` 函数`Array.filer`用法一直。参数`filePath`参数给出的就是`link`标签中`[href]`的值。\n这个方法，既不需要任何工具，也不需要删除什么。非常完美。[更改记录][use-plugin],[完整代码](https://github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo/tree/plugin)\n![使用 hook方式构建](/static/fina-build.png)\n\n\n这两种方法我个人比较倾向于方法三。由于 plugin 的代码比较简单，就不做 publish 了。需要的欢迎自取。\n本文章所涉及的[源码][source]。[方法二](#方法二)和[方法三](#方法三)在不同的分支，[点击查看最终效果][final]。\n\n![最终效果截屏](/static/final-screenshoots.gif)\n\n最后感谢[@xiyuyizhi][xiyuyizhi]提供的[宝贵思路](https://github.com/xiyuyizhi/notes/blob/master/js/theme.md)。\n本文纯属原创，如有错误欢迎指正。\n\n## 优化与改进\n上面方法存在一个比较严重的问题，就是需要在 `themes` 文件夹下手动建立多个主题文件。这样做一方面比较难维护，另一方面也会多很多的冗余。所以这里写了一个[脚本][generate-themes-script]，读取配置文件，并生成多个`theme.less`。\n\n## 最终实现\n最终写了一个[webpack-multiple-themes-compile][webpack-multiple-themes-compile]库，来完成上面所有的操作。只需要简单的几行配置！\n\n\n[webpack]:https://webpack.js.org/\n[less-multiple-color-theme-realize]:https://hiyangguo.github.io/2017/03/21/less-multiple-color-theme-realize/\n[gulp]:https://www.gulpjs.com.cn/\n[grunt]:https://gruntjs.com/\n[scope]:https://www.npmjs.com/package/css-loader#scope\n[xiyuyizhi]:https://github.com/xiyuyizhi\n[webpack构建下换肤功能的实现思路]:https://juejin.im/post/5a1b8a4df265da430f31d03e\n[initial-project]:https://github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo/tree/v0.0.1\n[init-webpack-config]:https://github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo/blob/v0.0.1/webpack.config.js\n[extract-text-webpack-plugin]:https://www.npmjs.com/package/extract-text-webpack-plugin\n[git-commit-extract-css]:https://github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo/commit/1509f94524626331731893253aec2d2ce6936911\n[html-webpack-handle-css-inject-plugin-js]:https://gist.github.com/hiyangguo/41b9d5fb9a164b1043ff1e5fbb7cdceb#file-htmlwebpackhandlecssinjectplugin-js\n[source]:https://github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo\n[exclude-css-file]:https://github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo/commit/90c1f24af526cd6d48bf6b095500e4ffa5c7f0e6\n[use-plugin]:https://github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo/commit/2688cead3298b65a4e5871ead1d261b008b545a8\n[final]:https://hiyangguo.github.io/webpack-mutiple-theme-bundle-css-demo/\n[generate-themes-script]:https://github.com/hiyangguo/webpack-mutiple-theme-bundle-css-demo/blob/master/script/generate-themes.js\n[webpack-multiple-themes-compile]:https://github.com/rsuite/webpack-multiple-themes-compile/blob/master/README_zh.md\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhiyangguo%2Fwebpack-mutiple-theme-bundle-css-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhiyangguo%2Fwebpack-mutiple-theme-bundle-css-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhiyangguo%2Fwebpack-mutiple-theme-bundle-css-demo/lists"}