笔记:配合 Vue.js 配置 Webpack(二)CSS、SCSS 样式文件配置


 拍摄于 2016-08-21
拍摄于 2016-08-21

处理 CSS

如前言中提到的,在 Webpack 中处理 CSS,入手的方法和打包 JavaScript 并没有区别—同样也是通过 loader,区别只是前者我们首先会去使用 babel-loader,而对于 CSS,可想而知,就是 css-loader。

为 Webpack 添加“处理 CSS 文件”的一般办法

需要处理 CSS,对 Webpack 来说就得配置新的 loader 来处理所有的 CSS 文件,对应的 loader 可想而知就叫做 css-loader。

安装 css-loader,并且在 webpack.config.js 中添加对应用来出来处理 CSS 文件的规则。

新建 CSS 入口文件 & 并在 JavaScript 中引入上一步新建的 CSS 文件

src/ 目录中,通过命令新建 css/main.css 文件,并写入一段测试用的 CSS 样式(效果是把当前网页背景变黑)。

通过 JavaScript 引入上一步新建的 CSS 文件。 在没有修改 Webpack 入口文件(当前是 app.js)情况下,直接在文件中引入 CSS 文件是最简单的做法(Webpack 中也支持“多入口”的文件引入方式,下文会有说明,目前保持最简单的方式演示,即从 JavaScript 中引入 CSS 的方式)。

app.js 中引入 main.css

随后启动 Webpack Dev Server,在浏览器中打开 http://localhost:1333/

会发现我们写的样式表什么用处都没


为什么启动 webpack-web-server 之后什么效果都没有,以及如何生效?

通过对生成的 bundle.js 进行搜索,可以确定我们之前写入的 CSS 声明 { background: #000000;},确实是被成功写入、并且被 Webpack 通过 css-loader 成功打包输出了的。

为什么在浏览器中打开的页面却没有任何效果? 要回答这个问题,不如想一下我们在“使用 Webapck 处理 CSS”这件事情上,到目前为止究竟干了些什么?

  1. 首先,第一步我们为 Webpack 编写了“用来处理 CSS 文件”的“规则”;
  2. 其次,我们新建了 CSS 文件(main.css)、并写了一小段样式进行测试;
  3. 接着,我们通过 JS 文件引入了 CSS 文件;
  4. 最后,我们运行了 Webpack,Webpack 在这里成功进行了对 JS、CSS 的打包、编译以及输出。

但我们打开浏览器,期待的“黑底白字”的页面并没有如期而至,仍然是上一步完成的“白底黑字”状态,那么是哪一步使得我们的操作成了彻底的无用功?又或者难道我们完全做错了?

期待的“黑底白字”的页面并没有如期而至,仍然是上一步完成的“白底黑字”状态。

答案是,与其说是“出问题的一步”,不如说是“与传统前端开发有区别的一步”,即“3. 接着,我们通过 JS 文件引入了 CSS 文件”。

一般的前端开发中,导入样式文件是通过 HTML 去引入 CSS,通过 <link> 标签;而我们在 Webpack 里则仅仅是通过 JS 去引入了 CSS,对,Webpack 并不会替我们自动完成“将 CSS 再引入到 HTML 使其生效”这一步。

那么如何让这些“通过 JS 引入的 CSS”,能够经由 Webpack Dev Server 使得目标 HTML 文件也可以成功读取呢?答案是 loader:style-loader(这里为引出 style-loader,才使用“通过 JS 引入 CSS”的例子。通过 Webpack 处理 CSS 不止一个方法,下文会提)。

同时想要引出的,还有 css-loader 和 style-loader 在使用上的一些区别

  • css-loader:是 Webpack 用来读取 CSS 样式表内容的 loader。Webpack 本身可以识别并且打包 JS 的模块,通过 css-loader,则新增了可以阅读 CSS 的能力。
  • style-loader: 有了阅读、解析 CSS 的能力,并不代表具有了可以把 CSS 输出给 HTML 的能力,于是就需要 style-loader,来将读取出来的样式信息注入到 HTML 中,来让浏览器“认可”、并且完成解析。

安装 style-loader,并按照文档上,或者简写方式如下修改 Webpack 中针对 CSS 的规则:

use: [] 之后数组,不论是简写的直接字符串 ['style-loader'],还是使用对象传入 [{ loader: 'style-loader'}],执行顺序都是从右至左,或者从下至上。在这里,也就是先执行 css-loader,再执行 style-loader,根据上面的例子,这里的意思就是:Webpack 先把通过 JS 引入的 CSS 样式进行处理,再通过 style-loader 将处理完毕 CSS 引入进 HTML。

Webpack 中的 loader 的执行顺序是从右至左,以上的意思就是,遇到 CSS 样式文件,先使用 css-loader 让 Webpack 有读取样式的能力,接着通过 style-loader 将已经读取到的样式注入到 HTML。

完成后重启 Webpack Dev Server,打开浏览器如期页面背景变成了纯黑(后来我在 main.css 中加了一句新的声明来让字体变成白色方便截图)。

完成后重启 webpack-dev-server,打开浏览器如期页面背景变成了纯黑(后来我在 main.css 中加了一句 color: #ffffff; 来让字体变成白色方便截图)。

预处理器,以 SCSS 为例

CSS 之后是 SCSS,理解了上面提到的几点,配置 SCSS 也就不那么困难。

sass-loader 的基本使用

总结起来,上文通过试错来获得的“成功配置 Webpack 解析、编译、输出 CSS”这一结果的过程中,会有三点,在之前、之后使用 Webpack 中都会经常遇到:

  1. Webpack 中使用 css-loader 来读取 CSS 样式表文件;
  2. Webpack 中使用 style-loader 来将读取的样式文件注入到 HTML 之中(使用 Webpack Dev Server)的时候;
  3. Webpack 中 loader 的执行是从右至左(基于配置的是否是简写,也可以是由下至上)的顺序。

理解这三点,在面对“需要处理 SCSS 文件”这个需求的时候,即便没有查看过任何教程,其实也可以做出合理的假设:SCSS 是作为扩展形式的“提升开发效率”的工具,不论如何在最后,(上线部署时)被引入到 HTML 文件中之前,都需要、也必须转换成 CSS 文件。

那么我们的处理的过程,对比原先“首先引入 CSS 文件”这一部分,在起始端,处理 CSS 之前,就需要加入对 SCSS 文件的处理机制(处理具体指的是将 SCSS 编译成浏览器“认可”的 CSS)。把整个处理过程想象成一个管道,SCSS -> CSS -> HTML(引入)。然后进一步推测,CSS 的处理是通过 loader,HTML 引入 CSS 也是通过 loader,那么针对 SCSS 处理,应该也是采用 loader?答案是:对,通过 sass-loader。

sass-loader 的安装、以及 Webpack 中针对 SCSS 的一般规则:

  1. sass-loader 本身还需要 node-sass 作为以来,需要一起安装;
  2. 上面说道,根据不同的配置书写方式,loader 的处理顺序会从右至左、或者右下到上,例子里是简单的写法,于是 loader 遵循从右至左的执行顺序,所以在起始端(即最右),加上第一步就需要的 sass-loader

其次,我们需要的实实在在的 SCSS 文件来进行测试:

修改 src/app.js 中的 import 语句,将原来引入的 main.css 替换成新建的 mian.scss 文件。

之后我们重新启动 Webpack Dev Server,通过浏览器打开 localhost:1333 查看,结果应该和之前没差别。

之后我们重新启动 Webpack Dev Server,应该和之前没有任何区别。

更进一步,可以将 SCSS 声明的文字颜色替换成红色,来检查 Webpack Dev Server 的工作效果是否和之前一致。

将 CSS 声明中原先的 background: #ffffff; 替换成红色:backgournd: #ff0000;,来检查 Webpack Dev Server 是否如期工作。

以上,就是最简单配置 Webpack 对 SCSS 文件的读取、编译、打包的基本配置。


通过 Webpack 配置中的 resolve.alias 选项,解决相对路径过长问题

上面,在 JS 引入 CSS、或者引入 SCSS 这一部分的例子,有经验的开发者可能已经意识会产生的问题:在一个文件(夹)组织十分深的项目中,采用相对路径来来对依赖进行引入,很可能造成让人头疼的结果,比如:

也就是:进、出很多很多层文件目录去找到目标依赖文件这一结果。

Webpack 为我们提供了一个 resolve.alias 选项,具体的作用:通过写入配置,在任何入口文件(entry)内,都可以通过“关键词”的形式,快速“指定”目标文件夹的位置。描述不免抽象,通过我们的配置文件直接举例。


sass-loader 配置中的一些选项

includePath

举例说明,首先在 node_moduels 文件夹下,新建一个 somepackage 目录,在这个目录下新建一个 options.scss 文件,写入简单的样式:

这个时候我们如果想要在 main.scss 中引入新建的这个 options.scss 文件,一般的方式如下,使用 ~ 符号就可以从 node_modules 目录下下找对应路径下的文件(“~”是 Webpack 提供的一个关键字, 使用功能和 resolve.alias 基本一致):

但如果想忽略前缀的 ./node_modules/,直接写成如下的形式,可以么?

可以,通过 includePaths 属性。includePaths 英语的字面意思就是将路径包括进来,简单理解就是将“预定义”的路径包含到“可以使用 @import 语句”的索引里面来。

重启 Webpack Dev Server,回到的 main.scss 中,将原本的红色 hex 值修改成 options.scss 中定义的 $red 变量,页面效果和之前的样子应该完全一致,没有丝毫变化。同时也证明了通过 includePath 声明而导入的变量也成功被编译。

main.scss 中,将原本的红色 hex 值修改成 options.scss 中定义的 $red 变量。

重新启动 Webpack Dev Server,页面上的效果应该完全一致。

SourceMap

这里说的 Source Map 针对的是 SCSS 文件 loader,并不是 Webpack 自带配置中的 devtool 属性可以选配的 source map,同样也用例子说明。

  1. 在上一步新建的 options.css 中写入的变量 $red: red;,以及 class: .red { color: $red};
  2. 存放 SCSS 文件的目录下,新建与上一步同名的文件 options.scss(上一步中 options.scss 是在 node_modules 下的 somepakcage 目录下创建),其中写入变量 $yellow: yellow,以及对应指定文字颜色的 class: .yellow { color: $yellow; };
  3. scss 目录下再次新建一个文件: ext.scss,写入变量 $blue: blue,以及对应指定文字颜色的 class: .blue { color: $blue;};
  4. 在 main.scss 中分别引入上面三步中的提到的文件。
  5. 在 HTML 中写入对应测试三个 class 的元素
  6. 分别添加 sourcemap: true 到三个 loader 的配置中;
  7. 重启 Webpack Dev Server。

打开浏览器就可以看到分别为红色、蓝色和黄色的文字,支持 Source Map 的浏览器“检查器”中也可以看到,样式对应的原始 SCSS 文件路径:比如蓝色的 blue 定义,就显示了该类的声明在 ext.scss,其它同理。

打开浏览器就可以看到分别为红色、蓝色和黄色的文字。

支持 Source Map 的浏览器“检查器”中也可以看到,样式对应的原始 SCSS 文件路径:比如蓝色的 blue 定义,就显示了该类的声明在 ext.scss,其它同理。


Webapck 单独配置 CSS(SCSS)入口

以上演示中,CSS 文件的引入是通过 JS 文件里使用 import 语句(或者 require)来引入,这样的方式很别扭。 自然,Webpack 不可能只支持单一文件,它为我们提供了直接在配置中声明多个“入口”的方法,通过简单地修改 module.entry 就能完成最基本的实现。

Webpack 为我们提供了直接在配置中声明多个“入口”的方法,通过简单地修改 module.entry 就能完成最基本的实现。

不只是 module.entry,作为输出的 module.output 也被做了修改,成为”使用方括号包裹的 [name.ext]“,它的意思是:根据传入设置中写入的“名称”和“文件类型(type)”,进行动态输出;否则,试想一下,如果保持之前的输出名“bundle.js”,那么我们单独传入的 CSS 的文件,也会被输出成 “bundle.js” 文件对不对?

同时,也因为配置了多入口,输出也成为了多个:一个 JS 文件和一个 CSS 文件。原来的情况是:Webpack 将 CSS 一起打包进入 bundle.js,之后通过在 HTML 中动态新建 style 标签的方式进行 CSS 样式的注入。对比现在,我们单独有了一个 CSS 文件,所以我们同时也需要动态更新 HTML 文件,将 CSS 通过 link 标签引入进来。

分离 CSS 至单文件

这一步要做的可能和上一步“Webapck 单独配置 CSS(SCSS)入口”有一些类似,所以先把区别说清楚:

Webapck 单独配置 CSS(SCSS)入口:我们将 “CSS 通过 JS 引入”这一步移除,直接通过 Webpack 的“多入口配置”对 CSS 进行单独单独引入,但问题是按照我们上面那种做法,即使我们引入了不同的 CSS (SCSS),最后通过编译获得的始终只是一个 app.js 文件(css 被包含其中);

分离 CSS(SCSS) 至单文件:通过引入 Webpack 插件,我们可以将引入 CSS(SCSS),分别压缩成不同的目标文件。比如,我们引入了 a.css 和 b.scss,b 文件中又引入了 c.scss 或者 d.scss,我们最终可以获得两个文件:a.css 和 b.css 的压缩完毕打包版本,而不是上一步那样只得到一个 app.css。

明白了需要做什么,搞清楚了之间的区别,接下来就是怎么做才能达成预期效果。很巧的是,Webpack 本身也不提供多种输出方式的选择,就和对 JS、CSS 和 SCSS 入口文件的处理我们依赖 loader 类似,对于输出的处理,我们需要依赖插件。

安装 mini-css-extract-plugin 插件:

使用(这里直接贴一份修改完毕的 webpack.config.js 的 patch,看上去很乱,下面大致解释做了些什么,同时文末会有清晰的版本):

  1. 做了一些调整,定义 isProd 常量,来存储现在是“开发环境”,还是“生产环境”,修改了 mode 选项的写法;
  2. 引入 mini-css-extract-plugin;
  3. 定义了,如果遇到入口文件是 CSS 或者 SCSS,同时也是“生产环境”时,编译、打包之后,不通过 style-loader 将内容动态输出到页面中(不通过 HTML 头部新建 HEAD 标签的形式),转而根据入口文件的“名称(例子中是 app)”,生成单独的 CSS 样式文件。
  4. 将原先生成的 module.output 中的 [name].[ext] 替换成 [name].js。

直接贴一份修改完毕的 webpack.config.js 的 patch,看上去很乱,下面大致解释做了些什么,同时文末会有清晰的版本。

重启 Webpack,不过这次需要输入的是部署生产环境的命令。稍作等待后,结果如期而至:app.js 和 app.css 成功生成(如果打开 app.css 会看到一些重复的对 body 的样式声明,因为测试的时候同时在 main.css 和 main.scss 中写了重复的样式,不需要惊讶)。

重新启动 Webpack,稍作等待后,结果如期而至:app.js 和 app.css 成功生成。

配置完 CSS、SCSS 后完整的 webpack.config.js

感谢阅读

你们好, 2018 年初把小站从 Jekyll 迁移到 Hugo 的过程中,删除了评论区放的 Disqus 插件,考虑有二:首先无论评论、还是对笔记内容的进一步讨论,读者们更喜欢通过邮件、或者 Twitter 私信的方式来沟通;其次一年多以来 Disqus 后台能看到几乎都是垃圾留言(spam),所以这里直接贴一下邮件、以及 Twitter 账户 地址。

技术发展迭代很快,所以这些笔记内容也有类似新闻的时效性,不免有过时、或者错误的地方,欢迎指正 ^_^。

BEST
Lien(A.K.A 胡椒)
本站总访问量 本站总访客量 本文总阅读量