前言
这一篇主要开始介绍使用webpack4
打包css
以及less
(stylus和sass同理
)
开始之前
经过上一篇Webpack4.X重修之路 --- 基础篇
现在我们拥有的项目结构如下:
├── package.json
├── scripts // webpack脚本
│ ├── webpack.common.js // 公用配置
│ ├── webpack.dev.js // 开发环境下配置
│ └── webpack.prod.js // 生产环境下配置
└── src
├── assets // 全局静态文件
│ └── img
├── config // 单独引用的全局配置
│ ├── ip.config.js
└── index.js // 入口文件
我们在src
目录下新建一个styles
目录,用于保存项目的一些样式文件,并在styles
下新建一个test.css
文件用于测试.
Webpack
最强大之处在于它有着很多的loader
可以处理不用的文件.
我们要打包css
文件主要用到几个
-
css-loader
: 用于使用import
引入css
文件 -
style-loader
: 将css
文件插入html
文件中 -
extract-text-webpack-plugin
: 将css
文件从js
文件中分离出来
安装
npm i -D extract-text-webpack-plugin style-loader css-loader
PS: 使用webpack4安装extract-text-webpack-plugin在打包时会出现警告, 解决方法: 安装 extract-text-webpack-plugin@next
使用
extract-text-webpack-plugin
是插件需要另外引入, loader
可以直接使用
// webpack.common.js
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
...
module: {
rules: [{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader']
})
}, ]
}
plugins: [
...
new ExtractTextPlugin('[name].css') // 传入的是打包后的文件路径以及文件名, 根目录为dist
]
PS
: 在生产模式下我们希望对css
文件进行压缩
需要用到
optimize-css-assets-webpack-plugin
cssnano
// webpack.prod.js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
plugins: [
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.less\.css$/g,
cssProcessor: require('cssnano'),
cssProcessorOptions: { discardComments: { removeAll: true } },
canPrint: true
}),
],
// 设置optimization.minimizer会覆盖webpack提供的默认值
//因此请务必同时指定JS minimalizer
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: false // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({})
]
}
})
Less
实际开发中大多数情况会使用css预编译器
以及一些插件
postcss-loader
-
autoprefixer
:用于自动补充前缀 -
less-plugin-functions
: 自定义less
函数 -
style-resources-loader
: 定义全局的样式文件 less
-
less-loader
: 用于处理less
文件
我们希望在开发过程
中只需要编译less
文件,生产模式
下才需要添加前缀并且压缩
- 开发模式
// webpack.dev.js
...
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const LessFunc = require('less-plugin-functions');
module.exports = merge(common, {
...
module: {
rules: [{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', {
loader: 'less-loader',
options: {
plugins: [new LessFunc()]
}
}, {
loader: 'style-resources-loader',
options: {
patterns: path.resolve(__dirname, '../src/styles/common.less')
}
}]
})
}, ]
}
})
style-resources-loader
的options.patterns
参数即为需要定义为共同样式的文件路径,定义之后可以在其他文件中直接使用次less文件中
的变量以及函数
- 生产模式
生产模式比开发模式多了压缩以及自动添加前缀的功能
// webpack.prod.js
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const Autoprefixer = require('autoprefixer');
const LessFunc = require('less-plugin-functions');
...
rules: [{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', {
loader: 'postcss-loader',
options: {
plugins: [
require('autoprefixer')({
browsers: ['last 5 versions']
})
]
}
}, {
loader: 'less-loader',
options: {
plugins: [new LessFunc()]
}
}, {
loader: 'style-resources-loader',
options: {
patterns: path.resolve(__dirname, '../src/style/common.less')
}
}]
})
}]
...
plugins: [
Autoprefixer
],
- 这一部分功能代码重复了,而且到现在我们才添加了处理
less
的,webpack
的配置文件代码就变得有点多了,我们可以使用webpack-chain
对现如今的配置进行重构.
先放重构前的代码
-
webpack.base.js
(原webpack.common.js)
const path = require('path');
const fs = require('fs');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: {
app: path.resolve(__dirname, '../src/index.js')
},
output: {
filename: 'js/[name].bundle.js',
path: path.resolve(__dirname, '../dist')
},
module: {
rules: [{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader']
})
}, ]
},
plugins: [
new CleanWebpackPlugin(),
new ExtractTextPlugin('[name].css'),
new CopyWebpackPlugin([
{ from: path.resolve(__dirname, '../src/config/*.js'), to: 'config/', toType: 'dir', flatten: true, ignore: ['*.md'] },
{ from: path.resolve(__dirname, '../src/assets/'), toType: 'dir', ignore: ['*.md'] }
]),
new HtmlWebpackPlugin({
inject: false,
template: require('html-webpack-template'),
title: '测试输出',
appMountId: 'app',
meta: [{
name: 'viewport',
content: 'width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0'
}],
favicon: path.resolve(__dirname, '../favicon.ico'),
headHtmlSnippet: getConfigScript('../src/config', 'config/')
})
]
}
/**
* 获取script标签字符串
* @param {String} source [源目标目录]
* @param {[String]} targetDir [生成的文件夹]
* @return {[String]} [指定文件夹下的js文件的script标签]
*/
function getConfigScript(source, targetDir) {
let configFiles = fs.readdirSync(path.resolve(__dirname, source), {});
let jsFiles = configFiles.filter(file => {
return file.indexOf('.js') !== -1;
})
let scripts = jsFiles.map(file => {
return `<script src="${targetDir + file}"> </script>`
})
return scripts.join('\n');
}
- webpack.dev.js
const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.base.js');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const LessFunc = require('less-plugin-functions');
module.exports = merge(common, {
mode: 'development',
output: {
publicPath: '/'
},
devtool: 'source-map',
devServer: {
contentBase: './dist',
host: '0.0.0.0',
port: 8001,
index: 'index.html',
open: true,
hot: true
},
module: {
rules: [{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', {
loader: 'less-loader',
options: {
plugins: [new LessFunc()]
}
}, {
loader: 'style-resources-loader',
options: {
patterns: path.resolve(__dirname, '../src/style/common.less')
}
}]
})
}, ]
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
})
- webpack.prod.js
const path = require('path');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const merge = require('webpack-merge');
const common = require('./webpack.base.js');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const Autoprefixer = require('autoprefixer');
const LessFunc = require('less-plugin-functions');
module.exports = merge(common, {
mode: 'production',
module: {
rules: [{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', {
loader: 'postcss-loader',
options: {
plugins: [
require('autoprefixer')({
browsers: ['last 5 versions']
})
]
}
}, {
loader: 'less-loader',
options: {
plugins: [new LessFunc()]
}
}, {
loader: 'style-resources-loader',
options: {
patterns: path.resolve(__dirname, '../src/style/common.less')
}
}]
})
}]
},
plugins: [
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.less\.css$/g,
cssProcessor: require('cssnano'),
cssProcessorOptions: { discardComments: { removeAll: true } },
canPrint: true
}),
Autoprefixer
],
// 设置optimization.minimizer会覆盖webpack提供的默认值,因此请务必同时指定JS minimalizer
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: false // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({})
]
}
})