webpack 样式加载的实现原理


Posted in Javascript onJune 12, 2018

加载css需要用到css-loader和style-loader css-loader将@import 和 url 处理成正规的ES6 import ,如果@import指向的是一个外部资源,css-loader会跳过,而只会对内部资源做处理。css-loader处理之后,style-loader会将输出的css注入到打包文件中。css默认是inline模式,且实现了HMR接口。但inline不太适用于生产环境(全部输出在页面上)。还需要用extracttextplugin生成一个单独的css文件,但先一步一步来。

一,样式打包

1.安装css-loader,style-loader

npm install css-loader style-loader --save-dev

2.修改webpack.config.js增加一个一级子节点

module:{
  rules:[{
  test:/\.css$/,
  use: ['style-loader', 'css-loader'],
  }]
 },

test的正则会匹配.css的文件。use中的执行顺序是从右到左。loader的执行是连续的,就像管道一样,先到css-loader再到style-loader。loaders: ['style-loader', 'css-loader'] 可以理解为:styleloader(cssloader(input)) 。

3.添加样式

app/mian.css

body {
 background: cornsilk;
}

然后在index.js中引入

import './main.css';

再运行npm start,在http://localhost:8080/中打开

webpack 样式加载的实现原理

这时候页面出现了背景色,而且发现样式写入了header中,这个时候你改变颜色,界面也会无刷新的更新,这正是上一节HMR的效果。

webpack 样式加载的实现原理

样式也是通过webpackHotUpdate方法进行更新。

二、加载less

再看一下如何加载less,先安装less-loader

npm install less less-loader --save-dev

再修改配置文件:

module:{
  rules:[{
   test: /\.less$/,
 
 use: ['style-loader', 'css-loader', 'less-loader'],  
  }]
 },

然后建立一个less文件。less.less

@base: #f938ab;

.box-shadow(@style, @c) when (iscolor(@c)) {
 -webkit-box-shadow: @style @c;
 box-shadow:   @style @c;
}
.box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) {
 .box-shadow(@style, rgba(0, 0, 0, @alpha));
}
.box {
 color: saturate(@base, 5%);
 border-color: lighten(@base, 30%);
 div { .box-shadow(0 0 5px, 30%) }
}

body {
 background: cornsilk;
}

修改index.js

import './less.less';
 import component from './component';

var ele=document.createElement("div");
ele.innerHTML="this is an box";
ele.className="box";
document.body.appendChild(ele);

let demoComponent=component();
document.body.appendChild(demoComponent);

得到效果:

webpack 样式加载的实现原理

可以看见编译成功,要注意的是,再使用less的时候import只能是less文件,这个时候再import main.css会报错。这一节对less就做一个简单的演示,其他样式预处理器同理,下面的内容还是继续基于css。

三、理解css作用域和css 模块

一般来说css的作用域都是全局的,我们常在母版页里面添加了多个样式文件,后面的样式文件会覆盖前面的样式文件,常常给我们的调试带来麻烦。而CSS Modules通过import引入了本地作用域。这样能够避免命名空间冲突。webpack的css-loader是支持CSS Modules的,怎么理解呢,先看几个例子。我们先在配置中开启(先关掉HMR):

module:{
  rules:[{
  test:/\.css$/,
  use: ['style-loader', {
  loader: 'css-loader',
   options: {
   modules: true,//让css-loader支持Css Modules。
  },
  },],

然后定义一个新的样式(main.css):

body {
 background: cornsilk;
}
.redButton {
 background: red;color:yellow;
}

给component加一个样式,先引入main.css。

import styles from './main.css';
export default function () {
 var element = document.createElement('h1');
  element.className=styles.redButton;
  element.innerHTML = 'Hello webpack';
 return element;
}

这个时候我们看到界面已经变化了。

webpack 样式加载的实现原理

再看右边生成的样式,我们的样式名称已经发生了改变。回顾整个过程相当于main.css中的每一个类名成了一个模块,在js中可以像获取模块一样的获取。但是你可能想,为毛我不能直接给元素赋值,干嘛要import呢。这是个好问题,我们再新增一个样式

不同样式文件的同名类

other.css

.redButton {
 background:rebeccapurple;color:snow;
}

它也有一个.redbutton的类(但效果是紫色的),然后在index.js中创建一个div元素并给它添加redbutton样式。

import './main.css';
import styles from './other.css';
import component from './component';

var ele=document.createElement("div");
ele.innerHTML="this is an other button";
ele.className=styles.redButton;
document.body.appendChild(ele);

let demoComponent=component();
document.body.appendChild(demoComponent);

再看效果

webpack 样式加载的实现原理

上面这个图说明了两问题,一个是我们在index.js中引入了2个样式文件,在index页面就输出了两个style,这让人有点不爽,但我们后面再解决。另外一个就是虽然两个样式文件中都有redButton这个类,但是这两者还是保持独立的。这样就避免了命名空间的相互干扰。如果你这个时候直接赋值

element.className="redButton";

这样是获取不到样式的。直接对元素的样式默认是全局的。

全局样式

如果想让某个样式是全局的。可以通过:global来包住。

other.css

:global(.redButton) {
 background:rebeccapurple;color:snow;
 border: 1px solid red;
}

main.css

:global(.redButton) {
 background: red;color:yellow;
}

这个时候redbutton这两个样式就会合并。需要直接通过样式名来获取。

element.className="redButton";

webpack 样式加载的实现原理

组合样式

我们再修改other.css,创建一个shadowButton 样式,内部通过composes组合redbutton类。

.redButton {
 background:rebeccapurple;color:snow;
 border: 1px solid red;
}
 
.shadowButton{
 composes:redButton;
 box-shadow: 0 0 15px black;
}

修改index.js:

var ele=document.createElement("div");
ele.innerHTML="this is an shadowButton button";
console.log(styles);
ele.className=styles.shadowButton;
document.body.appendChild(ele);

看一下是什么效果:

webpack 样式加载的实现原理

日志打印出来的是styles对象,它包含了两个类名。可以看见shadowButton是由两个类名组合而成的。div的class和下面的对应。

四、输出样式文件

css嵌在页面里面不是我们想要的,我们希望能够分离,公共的部分能够分开。extracttextplugin 可以将多个css合成一个文件,但是它不支持HMR(直接注释掉hotOnly:true)。用在生产环境挺好的

npm install extract-text-webpack-plugin --save-dev

先安装extracttextplugin这个插件,然后再webpack.config.js中进行配置:

const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractTxtplugin = new ExtractTextPlugin({
 filename: '[name].[contenthash:8].css',
});

const commonConfig={
 entry: {
 app: PATHS.app,
 },
 output: {
 path: PATHS.build,
 filename: '[name].js',
 },
 module:{
  rules:[{
   test:/\.css$/,
   use:extractTxtplugin.extract({
   use:'css-loader',
   fallback: 'style-loader',
   })
  }]},
 plugins: [
 new HtmlWebpackPlugin({
  title: 'Webpack demo',
 }),
 extractTxtplugin
 ],
}

一开始看到这个配置,让人有点懵。首先看fileName,表示最后输出的文件按照这个格式'[name].[contenthash:8].css',name默认是对应的文件夹名称(这里是app),contenthash会返回特定内容的hash值,而:8表示取前8位。当然你也可以按照其他的格式写,比如直接命名:

new ExtractTextPlugin('style.css')

而ExtractTextPlugin.extract本身是一个loader。fallback:'style-loader'的意思但有css没有被提取(外部的css)的时候就用style-loader来处理。注意到现在我们的index.js如下:

import './main.css';
import styles from './other.css';
import component from './component';

var ele=document.createElement("div");
ele.innerHTML="this is an box";
ele.className=styles.shadowButton;
document.body.appendChild(ele);

let demoComponent=component();
document.body.appendChild(demoComponent);

//HMR 接口
if(module.hot){
 module.hot.accept('./component',()=>{
  const nextComponent=component();
  document.body.replaceChild(nextComponent,demoComponent);
  demoComponent=nextComponent;
 })
}

引入了两个css文件。

这个时候我们执行 npm run build

webpack 样式加载的实现原理

再看文件夹得到一个样式文件。(如果不想看到日志可以直接npm build)

webpack 样式加载的实现原理

但是我们在第三部分使用了CSS Modules,发现other.css的样式没有打包进来。所以,我们的webpack.config.js还要修改:

module:{
  rules:[{
   test:/\.css$/,
   use:extractTxtplugin.extract({
   use:[ {
   loader: 'css-loader',
   options: {
   modules: true,
  },
  }],
   fallback: 'style-loader',
   })
  }]},

再次build。

webpack 样式加载的实现原理

发现两个样式打包成了一个文件。只要内容发生了变化,样式的名称就会变化。更多配置可以移步https://www.npmjs.com/package/extract-text-webpack-plugin

小结:这一篇讲的内容有点多了,从基本的样式打包,到less,然后认识CSS Modules。最后打包输出整个文件。可以说对于新手还是有点复杂,工具带来了便利性,自然也带来了学习的成本。诸多选择和诸多配置的最后,我们要找到一个适合我们自己的配置,并了解各个模块的机制才能面对不同需求的不同搭配。本节原码:http://xz.3water.com:81/201806/yuanma/webpack-ch4_3water.rar

参考:

https://www.npmjs.com/package/css-loader#local-scope

https://survivejs.com/webpack/styling/loading/

https://survivejs.com/webpack/styling/separating-css/

系列:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
漂亮的widgets,支持换肤和后期开发新皮肤(2007-4-27已更新1.7alpha)
Apr 27 Javascript
javascript下function声明一些小结
Dec 28 Javascript
JS解析json数据并将json字符串转化为数组的实现方法
Dec 25 Javascript
实现点击列表弹出列表索引的两种方式
Mar 08 Javascript
利用js制作html table分页示例(js实现分页)
Apr 25 Javascript
JS 在指定数组中随机取出N个不重复的数据
Jun 10 Javascript
jquery中$each()方法的使用指南
Apr 30 Javascript
JS选取DOM元素的简单方法
Jul 08 Javascript
javascript实现鼠标点击页面 移动DIV
Dec 02 Javascript
JS实现把一个页面层数据传递到另一个页面的两种方式
Aug 13 Javascript
AjaxFileUpload.js实现异步上传文件功能
Apr 19 Javascript
Vue项目中数据的深度监听或对象属性的监听实例
Jul 17 Javascript
js遍历添加栏目类添加css 再点击其它删除css【推荐】
Jun 12 #Javascript
webpack项目轻松混用css module的方法
Jun 12 #Javascript
微信小程序js文件改变参数并在视图上及时更新【推荐】
Jun 11 #Javascript
用vue快速开发app的脚手架工具
Jun 11 #Javascript
详解React Native 屏幕适配(炒鸡简单的方法)
Jun 11 #Javascript
vue非父子组件通信问题及解决方法
Jun 11 #Javascript
Angular如何在应用初始化时运行代码详解
Jun 11 #Javascript
You might like
php获得用户ip地址的比较不错的方法
2014/02/08 PHP
PHP使用ODBC连接数据库的方法
2015/07/18 PHP
PHP explode()函数的几个应用和implode()函数有什么区别
2015/11/05 PHP
javascript开发技术大全-第3章 js数据类型
2011/07/03 Javascript
使用Nodejs开发微信公众号后台服务实例
2014/09/03 NodeJs
jQuery中next方法用法实例
2015/04/24 Javascript
JavaScript数组迭代器实例分析
2015/06/09 Javascript
跟我学习javascript的函数和函数表达式
2015/11/16 Javascript
深入理解Node.js的HTTP模块
2016/10/12 Javascript
关于webuploader插件使用过程遇到的小问题
2016/11/07 Javascript
微信小程序城市定位的实现实例(获取当前所在国家城市信息)
2017/05/17 Javascript
详解SPA中前端路由基本原理与实现方式
2018/09/12 Javascript
如何在JavaScript中谨慎使用代码注释
2019/06/21 Javascript
微信小程序实现上传多张图片、删除图片
2020/07/29 Javascript
python定时检查启动某个exe程序适合检测exe是否挂了
2013/01/21 Python
Using Django with GAE Python 后台抓取多个网站的页面全文
2016/02/17 Python
用python一行代码得到数组中某个元素的个数方法
2019/01/28 Python
Python GUI编程完整示例
2019/04/04 Python
为何人工智能(AI)首选Python?读完这篇文章你就知道了(推荐)
2019/04/06 Python
Python网络爬虫之爬取微博热搜
2019/04/18 Python
在python中实现同行输入/接收多个数据的示例
2019/07/20 Python
Python3之字节串bytes与字节数组bytearray的使用详解
2019/08/27 Python
Django 实现 Websocket 广播、点对点发送消息的代码
2020/06/03 Python
python matplotlib绘制三维图的示例
2020/09/24 Python
详解使用HTML5的classList属性操作CSS类
2017/10/13 HTML / CSS
德国在线购买葡萄酒网站:Geile Weine
2019/09/24 全球购物
物理专业大学生职业生涯规划书
2014/02/07 职场文书
仓库管理员岗位职责
2014/03/19 职场文书
聘任书的写作格式及范文
2014/03/29 职场文书
作文评语大全
2014/04/23 职场文书
运动员获奖感言
2014/08/15 职场文书
嘉年华活动新闻稿
2015/07/17 职场文书
怎样做好公众演讲能力?
2019/08/28 职场文书
pytorch中Schedule与warmup_steps的用法说明
2021/05/24 Python
python 判断文件或文件夹是否存在
2022/03/18 Python
Linux磁盘管理方法介绍
2022/06/01 Servers