前言
這一篇主要開始介紹使用webpack4
打包css
以及less
(stylus和sass同理
)
開始之前
經(jīng)過上一篇Webpack4.X重修之路 --- 基礎(chǔ)篇
現(xiàn)在我們擁有的項目結(jié)構(gòu)如下:
├── package.json
├── scripts // webpack腳本
│ ├── webpack.common.js // 公用配置
│ ├── webpack.dev.js // 開發(fā)環(huán)境下配置
│ └── webpack.prod.js // 生產(chǎn)環(huán)境下配置
└── src
├── assets // 全局靜態(tài)文件
│ └── 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在打包時會出現(xiàn)警告, 解決方法: 安裝 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
: 在生產(chǎn)模式下我們希望對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
}),
],
// 設(shè)置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
實際開發(fā)中大多數(shù)情況會使用css預編譯器
以及一些插件
postcss-loader
-
autoprefixer
:用于自動補充前綴 -
less-plugin-functions
: 自定義less
函數(shù) -
style-resources-loader
: 定義全局的樣式文件 less
-
less-loader
: 用于處理less
文件
我們希望在開發(fā)過程
中只需要編譯less
文件,生產(chǎn)模式
下才需要添加前綴并且壓縮
- 開發(fā)模式
// 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
參數(shù)即為需要定義為共同樣式的文件路徑,定義之后可以在其他文件中直接使用次less文件中
的變量以及函數(shù)
- 生產(chǎn)模式
生產(chǎn)模式比開發(fā)模式多了壓縮以及自動添加前綴的功能
// 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
],
- 這一部分功能代碼重復了,而且到現(xiàn)在我們才添加了處理
less
的,webpack
的配置文件代碼就變得有點多了,我們可以使用webpack-chain
對現(xiàn)如今的配置進行重構(gòu).
先放重構(gòu)前的代碼
-
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
],
// 設(shè)置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({})
]
}
})