css 打包内部原理
假设有一个 css 文件,在src文件夹下
/** ./src/index.css **/
body{
background: red;
}
.cc{
background: green;
}
通常我们使用 import 导入css,如下:
/** ./src/index.js **/
import './index.css'
let div = document.createElement('div');
div.innerHTML = 'hello world';
div.className = 'cc';
document.body.appendChild(div);
这样 css 就生效了。
其实它中间经过了两个步骤,如下:
将 css 文件里的内容转化为字符串,通过 module.exports 导出,如下:
module.exports = ` body{ background: red; } `
然后创建一个style标签,将该字符串放入其中,最后将它插入到html中,如下:
``javascript let style = document.createElement('style').innerHTML(
body{ background: red; } `);
document.head.appendChild(style);
以上两个步骤这就是 css 打包的过程原理。分别对应了两个 loader (css-loader,style-loader)。
<a name="SLEWd"></a>
## css 打包实现
loader 其实就是翻译官(加载器),主要用于转化模块。<br />首先要安装css-loader,style-loader。
```shell
yarn add css-loader style-loader --dev
下面是 webpack 配置
/** ./webpack.config.js **/
let path = require('path');
let { CleanWebpackPlugin } = require('clean-webpack-plugin');
let HtmlWebpackPlugin = require('html-webpack-plugin');
let htmlPlugin = ['index'].map(chunkName => {
return new HtmlWebpackPlugin({
filename: `${chunkName}.html`,
inject: 'body',
chunks: [chunkName]
})
})
module.exports = {
mode: 'development',
entry: {
index: './src/index.js'
},
output: {
filename: '[name].[contenthash:8].js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ['**/*']
}),
...htmlPlugin
]
}
style-loader
style-loader 的内部原理是将 css 文件转换成字符串放入 style 标签中,然后将style 标签插入 header。
后面我们将自己实现一个 style-loader。
css-loader
css-loader的作用是帮我们分析出各个css文件之间的关系,把各个css文件合并成一段css。
下面是 css-loader 的一些常用属性:
module.exports = {
//...
module:{
rules:[
{
test:/\.css$/,
use:['style-loader', {
loader: 'css-loader',
options:{
url: false, //启用/禁用 url 解析 url(./rj.jpg)
import: false,//是否允许或者说禁用 @import语法处理 @import "base.css"
modules: true,//是否允许 css 模块
sourceMap: true, //是否生成 source-map
//importLoaders: true, 放在 css 兼容性的时候演示
esModule: true,// 默认情况下,css-loader 生成使用 ES Module 的模块对象,如果是false的话,不包装成ESMODULE
}
}]
}
]
}
//...
}
实现模块化(modules)
背景
不同的css文件,相同的类名可能导致样式被意外覆盖。css模块化可以使样式只作用于当前模块。原理就是将类名唯一化。
实现
设置 css-loader 的 modules 选项为 true。
let path = require('path');
let { CleanWebpackPlugin } = require('clean-webpack-plugin');
let HtmlWebpackPlugin = require('html-webpack-plugin');
let htmlPlugins = ['index'].map(chunkName => {
return new HtmlWebpackPlugin({
filename:`${chunkName}.html`,
inject: 'body',
chunks:[chunkName]
})
});
module.exports = {
mode: 'development',
entry: {
index: './src/index.js'
},
output: {
filename: '[name].[contenthash:8].js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test:/\.css$/,
use: ['style-loader', {
loader: 'css-loader',
options: {
modules: true
}
}]
}
]
},
plugins: [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ['**/*']
}),
...htmlPlugins
]
}
配置完成之后,模块化的 css 引用也要改变如下:
/** ./src/index.js **/
import style from './index.css';
let div = document.createElement('div');
div.innerHTML = 'hello world';
div.className = style['cc'];
document.body.appendChild(div);
结果
打包之后我们就会发现我们的 cc 类名变成了一串唯一的hash码,这就保证了在其他页面不会被样式覆盖或覆盖其他页面的样式。
解析路径(url)
案例
/** ./src/index.js 入口文件 **/
import './index.css';
/** ./src/index.css **/
body {
background: red;
background-image: url(./test.jpeg);
}
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
devServer: {
port: 8080,
open: true,
compress: true,
static: './dist',
},
entry: {
index: './src/index.js'
},
output:{
path: path.resolve(__dirname, './dist'),
filename: '[name].[hash:8].js'
},
module:{
rules: [
{
test: /\.css$/,
use:[
'style-loader',
{
loader:'css-loader',
options: {
url: false
}
}
]
}
]
},
plugins: [
new webpack.CleanPlugin(),
new HtmlWebpackPlugin({
filename:'index.html'
})
]
}
不解析的情况
我们们先将 url 设置为false,然后打包看结果。
打包文件如下:
页面结果如下:
我们发现 css 并没有解析 url上的路径。
解析的情况
下面我们设置为 true 试一下,如下:
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
devServer: {
port: 8080,
open: true,
compress: true,
static: './dist',
},
entry: {
index: './src/index.js'
},
output:{
path: path.resolve(__dirname, './dist'),
filename: '[name].[hash:8].js'
},
module:{
rules: [
{
test: /\.css$/,
use:[
'style-loader',
{
loader:'css-loader',
options: {
url: true
}
}
]
}
]
},
plugins: [
new webpack.CleanPlugin(),
new HtmlWebpackPlugin({
filename:'index.html'
})
]
}
打包文件如下:
页面文件如下:
这是我们就会发现图片被打包了,并且在使用时解析了url。
解析 @import 语法(import)
案例
/** ./src/index.js 入口文件 **/
import './index.css';
/** ./src/index.css **/
@import url('./other.css');
body {
background: red;
background-image: url(./test.jpeg);
}
.index {
color: blue;
}
/** ./src/other.css **/
.other{
color: green;
}
/** ./webpack.config.json **/
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
devServer: {
port: 8080,
open: true,
compress: true,
static: './dist',
},
entry: {
index: './src/index.js'
},
output:{
path: path.resolve(__dirname, './dist'),
filename: '[name].[hash:8].js'
},
module:{
rules: [
{
test: /\.css$/,
use:[
'style-loader',
{
loader:'css-loader',
options: {
url: false,
import: false
}
}
]
}
]
},
plugins: [
new webpack.CleanPlugin(),
new HtmlWebpackPlugin({
filename:'index.html',
inject: 'body',
template:'./public/index.html'
})
]
}
不解析的情况
我们先把 import 设置为 false,看一下打包结果
再看一下页面结果
我们发现 @import 语法被当作字符串插入,并没有被解析,字也没有改变颜色。
解析的情况
我们把 import 改成 true 试试看。如下:
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
devServer: {
port: 8080,
open: true,
compress: true,
static: './dist',
},
entry: {
index: './src/index.js'
},
output:{
path: path.resolve(__dirname, './dist'),
filename: '[name].[hash:8].js'
},
module:{
rules: [
{
test: /\.css$/,
use:[
'style-loader',
{
loader:'css-loader',
options: {
url: false,
import: true
}
}
]
}
]
},
plugins: [
new webpack.CleanPlugin(),
new HtmlWebpackPlugin({
filename:'index.html',
inject: 'body',
template:'./public/index.html'
})
]
}
看一下打包结果,发现 @import 已经没有的,说明被解析了
再看一下页面结果
发现 other 变成了绿色,样式也被成功解析。
ES模块化(esModule)
esModule 和 modules 不是一个东西,不能混为一谈。
esModule 是前端模块化,js 和 css 都有。而 modules 属性只是针对 css 作用域的。
当 esModule 设置为 true 时,通过 require 引入,会默认包一层 default。我们用找几个例子展示一下。
js 案例
/** ./title.js **/
module.exports = "title";
/** ./use.js **/
let title = require('./title');
console.log(title);
//如果 esModule 为 true,打印的值为 { defalut : 'title' }
//如果 esModule 为 false,则会直接打印 'title'