用 webpack 玩转博客园 ⛷

前置

自定义博客园样式需要一下几部分

  • 页面定制 CSS 代码
  • 博客侧边栏公告(支持 HTML 代码) (支持 JS 代码)
  • 页首 HTML 代码
  • 页脚 HTML 代码

你可能不熟悉 页首 HTML 代码 ,可以在此处放入一个 loading,因为页面加载时会最先加载这部分。总之,页面定制 CSS 代码博客侧边栏公告(支持 HTML 代码) (支持 JS 代码) 是最重要的两部分。

方式

自定义你的皮肤可以使用下面这几种方式:

css in js

通过 webpack 我们可以实现将 css 打包到 js, 样式由 js 动态添加。使用时只需要这样:

  1. 引入打包好的 js
  2. 给你的皮肤添加一些配置(可选的)
<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!可以用它快速地构建、安装、分享你的博客园皮肤。

GZ/awescnb