用 webpack 玩转博客园 ⛷
前置
自定义博客园样式需要一下几部分
- 页面定制 CSS 代码
- 博客侧边栏公告(支持 HTML 代码) (支持 JS 代码)
- 页首 HTML 代码
- 页脚 HTML 代码
你可能不熟悉 页首 HTML 代码 ,可以在此处放入一个 loading,因为页面加载时会最先加载这部分。总之,页面定制 CSS 代码 和 博客侧边栏公告(支持 HTML 代码) (支持 JS 代码) 是最重要的两部分。
方式
自定义你的皮肤可以使用下面这几种方式:
css in js
通过 webpack 我们可以实现将 css 打包到 js, 样式由 js 动态添加。使用时只需要这样:
- 引入打包好的 js
- 给你的皮肤添加一些配置(可选的)
<script src="//guangzan.gitee.io/awescnb/index.js"></script>
<script>$.awesCnb({
// 给你的皮肤添加一些配置
})
</script>
如果不暴露配置直接引入一个 js 就好了,是不是非常简单?
缺点
页面不能及时渲染 css,因为 css in js,但是我们可以加个 loading 解决。 😀
优点
这样做甚至可以实现瞬间切换多套皮肤,这里有我以前做的一个例子供您查看。点击查看切换效果.
css && js
上面那样做的缺点很明显, 需要一个 loading, 如果我们将 css 和 js 分离,把 css 放到 页面定制 CSS 代码,js 放到 博客侧边栏公告(支持 HTML 代码) (支持 JS 代码) 就不会出现这种情况了。通过 webpack plugin MiniCssExtractPlugin
可以将皮肤代码分别打包出一个 js 文件和一个 css 文件。
webpack
如何写一个架子方便开发博客园皮肤呢,下面我把 webpack 配置放出来。
options.js
我把需要经常更改的配置单独抽离一个文件,这样做能节省我很多时间。
module.exports = {
themeName: 'reacg',
template: 'post',
eslint: true,
sourceMap: false,
openAnalyzer: false,
cssExtract: false,
}
- themeName 创建的主题文件夹名称 (运行 npm start 会启动它)
- template 本地开发要启动的页面 ‘index’ -> 首页 ‘post’ -> 随笔详情页 ‘tag’ -> 标签页 …
- eslint 是否开启 eslint
- sourceMap 是否开启 sourcemap
- openAnalyzer build 时开启 size 分析
- cssExtract 是否单独抽离 css
cssExtract
如果没有开启,build 会打包生成一个 dist, dist 下仅有 js 文件,如上, 这是 css in js 的方式,通过 js 动态添加 style
; 如果设为 true ,会在 dist 目录下创建一个 ext 文件夹, 下面放了你的皮肤 js 和 css 文件。
正如你所见, ext 下每一个皮肤对应 js 和 css 两个文件。
webpack.base.js
不言而喻, webpack.base.js 是开发环境和生产环境依赖的公共配置。
const path = require('path')
const {themeName, eslint} = require('./options')
const jsLoader = [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
]
if (eslint) {
jsLoader.push({
loader: 'eslint-loader',
options: {
cache: true,
},
})
}
module.exports = {
entry: {
// 多出口
index: './src/main.js',
acg: './src/themes/acg/index.js',
reacg: './src/themes/reacg/index.js',
gshang: './src/themes/gshang/index.js',
element: './src/themes/element/index.js',
[themeName]: `./src/themes/${themeName}/index.js`,
},
output: {
filename: '[name].js',
path: path.join(__dirname, '..', 'dist'),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: jsLoader,
},
],
},
resolve: {
alias: {
'@': path.resolve('src'),
'@awescnb': path.resolve('src/awescnb'),
'@tools': path.resolve('src/assets/utils/tools'),
'@plugins': path.resolve('src/plugins'),
'@constants': path.resolve('src/constants'),
},
},
}
这里主要关注多个 entry, 方便打包多个皮肤。
webpack.dev.js
开发环境配置
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const {template, themeName, sourceMap} = require('./options')
module.exports = merge(baseWebpackConfig, {
mode: 'development',
devtool: sourceMap ? 'inline-source-map' : '',
devServer: {
host: 'localhost',
port: 8080,
contentBase: path.join(__dirname, 'dist'),
open: true,
hot: true,
disableHostCheck: true,
proxy: {},
before() {},
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: `src/templates/${template}.html`,
inject: 'body',
chunks: [`${themeName}`],
}),
new webpack.HotModuleReplacementPlugin({}),
],
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
},
},
'postcss-loader',
],
},
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
},
},
'postcss-loader',
'sass-loader',
],
},
],
},
})
webpack.prod.js
const path = require('path')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base')
const {openAnalyzer, cssExtract} = require('./options')
// const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const plugins = [
// new CleanWebpackPlugin()
]
let output = {
filename: '[name].js',
path: path.join(__dirname, '..', 'dist'),
}
let cssLoader = [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
},
},
'postcss-loader',
]
let scssLoader = [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
},
},
'postcss-loader',
'sass-loader',
]
if (openAnalyzer) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'server',
analyzerHost: '127.0.0.1',
analyzerPort: 8888,
reportFilename: 'report.html',
defaultSizes: 'parsed',
openAnalyzer: true,
generateStatsFile: false,
statsFilename: 'stats.json',
statsOptions: null,
logLevel: 'info',
})
)
}
if (cssExtract) {
output.path = path.join(__dirname, '..', 'dist/ext')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
plugins.push(
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
ignoreOrder: false,
})
)
const MiniCssExtractPluginLoader = {
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
hmr: process.env.NODE_ENV === 'development',
},
}
cssLoader[0] = MiniCssExtractPluginLoader
scssLoader[0] = MiniCssExtractPluginLoader
}
module.exports = merge(baseWebpackConfig, {
mode: 'production',
output,
plugins,
externals: {
jquery: 'window.jquery',
},
module: {
rules: [
{
test: /\.css$/,
use: cssLoader,
},
{
test: /\.scss$/,
use: scssLoader,
},
],
},
})
通过简单的配置就可以实现任何你想要的结果。webpack 越来越火不是没有原因的啊,快速上手, 生态丰富。如果 cli 用惯了,不如自己尝试打造自己的工作流。如果你没有时间造这个轮子,我已经将他封装好了, 分享给大家。 free to use!可以用它快速地构建、安装、分享你的博客园皮肤。