CSS 模块入门

Avatar of Robin Rendle
Robin Rendle 发布

DigitalOcean 为您旅程的每个阶段提供云产品。开始使用 200 美元的免费积分!

CSS 模块并没有唯一的处理 JavaScript 模板、CSS 文件或构建步骤的方法来使其工作。在这篇文章中,它是关于 CSS 模块系列的一部分,我们将探讨一种方法。这篇文章的目的是让一个 CSS 模块项目能够运行起来。

文章系列

  1. 什么是 CSS 模块,为什么我们需要它们?
  2. CSS 模块入门 (您现在在这里!)
  3. React + CSS 模块 = 😍

在我参与的项目中,有一个要求是 CSS 永远不应该依赖于客户端 JavaScript 来工作,因此构建步骤需要在部署之前将所有内容处理成可用的 HTML 和 CSS。我们将使用 webpack,一个构建系统和模块打包器。在下一篇文章中,我们将重点介绍如何使下面的代码适合于一个向浏览器渲染静态 HTML 的真实项目。

让我们开始吧!

安装 webpack

安装 NPM 和 node 之后,我们需要在某个地方设置一个空目录并运行以下命令

npm init --y

这将创建一个 package.json 文件并用一些默认值填充它。这是我们的依赖项清单——当其他人npm install 此项目时,下载和安装内容的说明。

webpack 将处理我们的构建过程。它将监视我们的 CSS、JavaScript 和 HTML,并在两者之间执行所有操作。但是 webpack 是什么?Maxime Fabre 想知道 webpack 是构建系统还是模块打包器

好吧,它两者都是——我说的不是它同时执行这两者,而是它将两者结合起来。webpack 不会构建您的资产,然后单独捆绑您的模块,它将您的资产视为模块本身……可以导入、修改、操作,最终可以打包到您的最终包中。

如果这听起来很奇怪,别担心。还记得 Sass、Gulp 和 npm 都是不熟悉和可怕的时候吗?我们会弄清楚的。

让我们通过创建一个 JavaScript 文件来定义一个依赖项,以便我们可以导入该代码块,来确保 webpack 正确地“捆绑”模块。首先,我们需要全局安装 webpack,这将使我们能够在终端中使用webpack命令

npm install webpack -g

完成后,我们需要在我们的项目中本地安装 webpack,如下所示

npm i -D webpack

现在我们需要在/src目录中创建一个index.js文件。通常我喜欢创建一个目录,其中包含所有静态资产(例如图像、字体、CSS 文件和标记)。我编写的任何代码通常都位于/src目录中,而由机器编写或在特定过程中解释的任何代码都应位于/build目录中。我的想法是,删除/build目录应该完全没问题,并且不会遇到任何问题,因为我们只需运行一个命令,它就会处理/src目录中的内容并完全重建/build目录。在这种情况下,我们希望 webpack 查看/src中的所有内容,执行某个过程,然后将该代码移动到/build中。

/src目录中,我们还可以添加一个空alert.js文件(我们将在稍后返回)。我们还需要一个webpack.config.js文件,该文件位于项目的根目录下,/src目录之外,因此我们的项目结构现在应该如下所示

package.json
webpack.config.js
/node_modules
/src
  index.js
  alert.js

webpack.config.js(一个用于配置 webpack 的文件)中,我们可以添加以下内容

module.exports = {
  entry: './src',
  output: {
    path: 'build',
    filename: 'bundle.js',
  },
};

从现在开始,每当我们运行webpack命令时,webpack 将查看/src中的所有资产以构建依赖项树。

回到我们的src/index.js文件,我们可以添加这个

require("./alert.js");

在我们的alert.js文件中,我们可以编写这个

alert("LOUD NOISES");

现在让我们在根目录中创建一个index.html文件,并在关闭之前添加一个脚本标签中的捆绑包



    
    

CSS 模块演示


    <script src="build/bundle.js"></script>

bundle.js将由 webpack 生成。要生成它,我们只需运行webpack命令。为了使这更容易,我们可以使用构建脚本更新我们的package.json文件。这是您应该在该文件中找到的内容

"scripts": {
  "test": "echo 'Error: no test specified' &amp;&amp; exit 1"
},

这些是npm给我们的默认值,但我们可以用以下代码替换上面的代码,以创建我们自己的命令行脚本,该脚本将为我们运行 webpack 并打开一个浏览器窗口

"scripts": {
  "start": "webpack &amp;&amp; open index.html"
},

因此,每当我们运行npm start时,我们都会自动运行webpack命令并在浏览器中打开我们的索引文件。让我们现在这样做,看看会发生什么。

万岁,有些东西正在工作!这证明了我们的index.js文件正在从alert.js导入我们的代码,并且 webpack 正在正确地捆绑所有内容。如果我们现在删除alert.js文件,我们将在再次运行npm start时发现错误

如果 webpack 找不到导入的模块,它将显示此错误。但是现在我们已经确认所有这些都正常工作,我们可以删除index.js文件中的require语句,并继续学习 Webpack 的下一步。

添加我们的第一个加载器

webpack 中的加载器非常重要。Maxime Fabre 在 主题 上这样说道

加载器是小型插件,基本上表示“当您遇到此类文件时,对其执行此操作”。

在 Maxime 的教程中,他添加了 Babel 加载器,这是一个非常好的起点,因为 Babel 允许我们使用 ES2015 和 JavaScript 语言的最新改进。因此,我们不再使用前面用于require另一个模块的 Common.js 函数,而是可以使用import。使用 Babel,我们还可以使用类、箭头函数和 许多其他很酷的功能

像 Babel 这样的工具允许我们今天编写新的 ES2015 代码,并执行称为转译(类似于预处理)的任务,将代码转换为具有更大浏览器支持的早期版本的 JavaScript。这类似于 Sass 的工作原理;最初以 Sass 语法编写代码,然后预处理器编译为标准 CSS。

以下将安装 webpack Babel 加载器以及我们运行 Babel 所需的依赖项

npm i -D babel-loader babel-core babel-preset-env

在项目根目录下的.babelrc文件中,我们可以配置预设以让其他人知道我们将使用哪个 JavaScript 语法

{
  "presets": ["babel-preset-env"]
}

现在我们希望对所有.js文件运行 Babel,但仅限于我们编写的文件,我们以后安装的任何其他依赖项可能都有自己的语法,我们不想弄乱该代码。这就是 webpack 加载器发挥作用的地方。我们可以打开webpack.config.js文件并将该代码替换为此

module.exports = {
  entry:  './src',
  output: {
  path: 'build',
    filename: 'bundle.js',
  },
  module: {
    loaders: [
      {
        test: /\.js/,
        loader: 'babel-loader',
        include: __dirname + '/src',
       }
    ],
  }
};

loaders数组内的test键/值对是我们告诉 webpack 我们想要对哪种类型的文件执行操作的方式,而include则准确地告诉它我们希望在项目中的哪个位置执行该操作。

让我们测试 Babel 是否与 webpack 协同工作。在一个新文件(src/robot.js)中,让我们编写以下内容

const greetings = (text, person) =&gt; {
  return `${text}, ${person}. I read you but I’m sorry, I’m afraid I can’t do that.`;
}

export default greetings;

此 JavaScript 文件使用了一些 ES2015 特定的功能,例如exportconstlet、箭头函数和模板文字。

现在我们可以将该模块import到我们的src/index.js文件中,如下所示

import greetings from './robot.js'
document.write(greetings("Affirmative", "Dave"));

最后,我们需要做的就是再次运行npm start,我们的浏览器应该弹出文本:“肯定的,戴夫。我读懂了,但很抱歉,恐怕我不能那样做。”这仅仅证实了 Babel 正在按预期工作。

万岁!那还不是 CSS 模块,尽管我们肯定更近了一步。但在继续之前,让我们删除src/robot.jssrc/index.js中的所有代码。

加载样式

现在我们已经让我们的模板几乎可以工作了,我们需要添加两个加载器:css-loaderstyle-loader,我们将安装它们

npm i -D css-loader style-loader

css-loader 获取 CSS 文件并读取其所有依赖项,而 style-loader 将直接将这些样式嵌入到标记中。让我们通过在src/app.css中编写一些 CSS 来测试它

.element {
  background-color: blue;
  color: white;
  font-size: 20px;
  padding: 20px;
}

然后我们可以将该样式表import到我们的src/index.js文件中

import styles from './app.css'

let element = `

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequatur laudantium recusandae itaque libero velit minus ex reiciendis veniam. Eligendi modi sint delectus beatae nemo provident ratione maiores, voluptatibus a tempore!

`

document.write(element);

等等,等等!我们刚刚是不是让一个样式表成为了 JavaScript 文件的依赖项?没错,我们确实这么做了。但在它正常工作之前,在我们了解到这为什么有用之前,我们需要再次重新配置我们的 webpack.config.js 文件。

module.exports = {
  entry:  './src',
  output: {
    path: 'build',
      filename: 'bundle.js',
    },
  module: {
    loaders: [
      {
        test: /\.js/,
        loader: 'babel',
        include: __dirname + '/src',
      },
      {
        test: /\.css/,
        loaders: ['style', 'css'],
        include: __dirname + '/src'
      }
    ],
  }
};

运行 npm start 将得到类似这样的结果。

因此,如果我们在文档上“检查元素”,我们会发现 style-loader 已将该文件放入

<style> 标签中,位于文档的 <head> 部分。

让我们总结一下刚刚发生了什么。我们创建了一个 JavaScript 文件,它请求另一个 CSS 文件,然后该代码被嵌入到网页中。因此,在一个更真实的例子中,我们可以创建一个 buttons.js 文件,并使 buttons.css 成为它的依赖项,然后将该 JavaScript 导入到另一个文件中,该文件组织我们的模板并输出一些 HTML。这应该会使我们的代码变得非常模块化且易于阅读!

就我个人而言,为了保持代码整洁,我更倾向于使用单独的 CSS 文件,而不是将所有代码内联。为此,我们需要使用一个名为 webpack 插件extract text,它

将入口块中的每个 require('style.css') 移动到一个单独的 css 输出文件中。因此,您的样式不再内联到 JavaScript 中,而是位于一个单独的 css 捆绑文件中 (styles.css)。如果您的样式表总量很大,它会更快,因为样式表捆绑包与 JavaScript 捆绑包并行加载。

我们必须使用 npm 安装它。

npm i -D extract-text-webpack-plugin

现在我们可以再次更新我们的 webpack.config.js 文件,通过 require 它并将我们的 CSS 加载器放入其中。

var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  entry:  './src',
  output: {
    path: 'build',
    filename: 'bundle.js',
  },
  module: {
    loaders: [
      {
        test: /\.js/,
        loader: 'babel',
        include: __dirname + '/src',
      },
      {
        test: /\.css/,
        loader: ExtractTextPlugin.extract("css")
      }
    ],
  },
  plugins: [
    new ExtractTextPlugin("styles.css")
  ]
};

ExtractTextPlugin 现在将为我们创建一个 styles.css 文件!

您可能已经注意到,我们完全去掉了 style-loader。这是因为我们不再希望这些样式注入到我们的标记中。因此,现在如果我们打开 /build 目录,应该会发现已创建了一个包含所有代码的 styles.css 文件。然后在我们的 index.html 文件中,我们现在可以在 <head> 中添加我们的样式表。

<link rel="stylesheet" href="build/styles.css">

再次运行 npm start,然后bam! – 我们的样式神奇地回到了它们所属的页面上。

现在我们的 CSS 和 HTML 在页面上正常工作了,我们如何操作类名以获得局部作用域的所有好处?我们只需像这样更新我们的 webpack.config.js 文件即可。

{
  test: /\.css/,
  loader: ExtractTextPlugin.extract('css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'),
}

这会将生成的随机文本添加到类名的末尾。CSS Modules 实际上就是这样,一个散列值,它会更改类名,可以通过 webpack 中的 CSS 加载器添加这些类。

接下来,我们必须使用 styles.element 类更新我们的 index.js 文件。

import styles from './app.css'

let element = `
  <div class="${styles.element}">
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequatur laudantium recusandae itaque libero velit minus ex reiciendis veniam. Eligendi modi sint delectus beatae nemo provident ratione maiores, voluptatibus a tempore!
  </div>
`

document.write(element);

看看发生了什么!再运行一次 npm start,我们的代码现在已经被 webpack 处理,所以局部作用域不再是问题,因为注入到网页中的类现在看起来像这样。

<div class="app__element___1MmQg">
  ...
</div>

我们还没有真正完成,因为还有很多问题没有得到解答。我们如何在开发环境中编写这样的代码?我们如何绕过我们用来将标记注入页面的那个讨厌的 document.write 规则?我们应该如何组织我们的模块和文件?让 CSS Modules 运行起来只是工作的一半,接下来我们需要考虑如何将代码库从另一个系统移植到其中。

在下一个教程中,我们将了解 React 如何帮助我们生成整洁的小模块,此外,我们还将了解如何从多个模板生成静态标记,以及如何向我们的项目添加 Sass 和 PostCSS 等其他功能。

文章系列

  1. 什么是 CSS 模块,为什么我们需要它们?
  2. CSS 模块入门 (您现在在这里!)
  3. React + CSS 模块 = 😍