本文介绍了另一种实现主题功能的思路,是前一篇([ scss | webpack ] 实现页面主题思路)的扩展。
此方法通过切换style的外部样式表链接实现。
效果图
Demo项目结构
|____src
| |____styles # 页面样式
| | |____core.scss
| |____themes # 主题变量
| | |____default.scss
| | |____dark.scss
| | |____blue.scss
| |____entry.js # 打包入口
|____remove-theme-js-plugin.js
|____webpack.config.js
|____package.json
页面样式与主题变量
/* src/styles/core.scss */
body {
background-color: $primary;
}
.text {
color: $text-color;
border: 2px solid $border-color;
}
/* src/themes/default.scss */
$primary: #17a2b8 !default;
$text-color: #212529 !default;
$border-color: #6c757d !default;
/* src/themes/blue.scss */
$primary: #007bff;
/* src/themes/default.scss */
$primary: #343a40;
$text-color: #f8f9fa;
$border-color: #e9ecef;
入口文件
const styleLink = document.createElement('link')
styleLink.rel = 'stylesheet'
styleLink.href = 'theme/default.css'
document.body.appendChild(styleLink)
const span = document.createElement('span')
span.classList.add('text')
span.innerHTML = '文字'
document.body.appendChild(span)
const themeNames = ['', 'blue', 'dark']
const themeSwitchDiv = document.createElement('div')
themeNames.forEach(name => {
name = name || 'default'
const btn = document.createElement('button')
btn.innerHTML = name
btn.addEventListener('click', () => {
styleLink.href = `theme/${name}.css`
})
themeSwitchDiv.appendChild(btn)
})
document.body.appendChild(themeSwitchDiv)
remove-theme-js-plugin.js
// 移除把scss当做入口而产生的js文件
class RemoveThemeJsPlugin {
apply(compiler) {
compiler.hooks.emit.tap('RemoveThemeJsPlugin', compilation => {
Object.keys(compilation.assets)
.filter(file => /^theme\/.+\.js/.test(file))
.forEach(file => {
console.log(file)
delete compilation.assets[file]
})
})
}
}
module.exports = RemoveThemeJsPlugin
webpack打包配置
const RemoveThemeJsPlugin = require('./remove-theme-js-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const fs = require('fs')
const path = require('path')
const themePath = path.resolve(__dirname, 'src/themes')
const styleCorePath = path.join('src', 'styles', 'core.scss')
const getFilename = filepath => path.parse(filepath).name
// 获取主题入口
const getThemeEntry = () => {
const files = fs.readdirSync(themePath)
entries = {}
files.forEach(name => {
const filename = getFilename(name)
entries['theme/' + filename] = path.join(themePath, name)
})
return entries
}
const themeEntry = getThemeEntry()
module.exports = {
mode: 'production',
entry: { main: path.resolve(__dirname, 'src/entry.js'), ...themeEntry },
output: { path: path.resolve(__dirname, 'dist') },
plugins: [
// 打包时清理输出文件夹
new CleanWebpackPlugin(),
// 样式提取到 .css 文件中
new MiniCssExtractPlugin(),
// 创建 HTML 文件,使用excludeChunks排除入口
new HtmlWebpackPlugin({
title: '主题风格',
excludeChunks: Object.keys(themeEntry)
}),
new RemoveThemeJsPlugin()
],
module: {
rules: [
{
test: /\.scss$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
'css-loader',
{
loader: 'sass-loader',
options: {
additionalData: (content, loaderContext) => {
const { resourcePath, rootContext } = loaderContext
const relativePath = path.relative(rootContext, resourcePath)
if (relativePath.includes(path.join('src', 'themes'))) {
// 在尾部引入样式
content += '@import "../styles/core.scss";'
}
return content
}
}
}
]
}
]
}
}