通过渲染编译 JS 模板和 CSS 文件来实现 CSS 模块化的方式不止一种。在今天的教程中,我们只着重讨论一种在项目中应用 CSS Modules 的实现方式:

在我之前经手的一个项目中,有一个需求是 CSS 不能依赖客户端的 JS 文件。也就是说在部署之前必须打包构建好所有的 HTML/CSS 文件。我们当时用的是 Webpack 模块构建工具。文章接下来会介绍如何实现上述这个需求。

本篇教程中的代码示例在:discountry/webpack-css-modules-example

安装Webpack

在安装完NPM和Node之后,我们需要新建一个空文件夹,然后在该目录下运行:

npm init --y

这个命令会按照默认设置生成一个package.json文件。此文件主要包含了某个项目的依赖列表,也就是在我们运行npm install时会被安装的包都写在这个文件里面。

我们用Webpack来完成构建步骤。它可以监听CSS/JavaScript/HTML文件,并处理所有的中间环节。我差点忘了你可能还不了解Webpack到底是什么。你可以把它理解为一个构建系统或打包工具:

事实上它是两者的结合。Webpack会将你项目中所有的资源(图片/CSS/HTML/JS)视为模块,你可以导入、编辑、操作、并打包到你最终的输出文件中。——Maxime Fabre

即使这听起来很奇怪也请别担心。在Sass和Gulp还有NPM刚刚出来的时候,一时间也很难让人接受。

首先让我们配置好Webpack确保它能正常运作。第一步需要在全局安装Webpack,这样你才可以在命令行里使用它:

npm install webpack -g

之后再在你的项目中安装Webpack

npm i -D webpack

现在我们需要在项目中创建一个index.js文件,把它放到/src文件夹里。我个人习惯于把所有的静态资源(图片/字体/CSS等)存在一个文件夹里,自己写的代码存在/src文件夹内,而电脑生成的代码则会放在/build文件夹。我们并不需要手动创建/build文件夹,可以让Webpack自动完成将/src下的代码打包到/build的操作。

在/src文件夹里我们可以先创建一个名为alert.js的文件。另外还需要在项目的根目录创建webpack.config.js配置文件。现在我们的项目结构大概是下面这个样子:

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

在webpack.config.js配置文件中,写入以下内容:

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文件,然后添加一个链接到打包输出文件的script标签:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document name</title>
</head>
<body>
    <h1>CSS Modules demo</h1>

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

这里的bundle.js就是webpack打包之后会生成的文件。我们只需要在项目中运行webpack命令就可以生成它了。为了更方便一些,我们可以在package.json中写上构建的脚本:

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

这一段是npm默认的脚本配置,我们可以把它替换成如下内容:

"scripts": {
    //如果是Windows则改为 webpack && start index.html
  "start": "webpack && open index.html"
},

之后在项目的命令行中输入npm start命令,我们就能自动完成webpack的构建并在浏览器中打开网页查看结果。

看起来效果不错!目前为止webpack的功能应该都走通了。你可以删掉alert.js测试一下,我们再次试着运行npm start时就会看到错误信息。

在Webpack无法找到我们导入的模块时就会报错。接下来让我们更深入地了解一下webpack吧。

配置加载器(Loader)

加载器(Loader)是webpack的使用中非常重要的一部分。你可以把加载器看作一个小插件,在我们导入特定类型的文件时,对应的加载器就会起作用。

例如使用Babel加载器可以允许我们使用一些ES6的新特性来写JS代码。不需要像刚才那样使用require而是可以使用import来导入模块,以及箭头函数之类的新的特性

Babel起到一个类似于编译器或预处理器的作用,它允许我们用ES6的语法书写,之后把我们的代码转换成可以兼容运行的ES5代码。

通过下面这行命令来安装Babel的相关依赖:

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

在根目录创建一个.babelrc文件,在里面设置:

{
  "presets": ["es2015"]
}

现在我们来配置,让Babel处理所有我们自己编写的.js文件。注意配置好,不能让Babel干扰一些可能会使用的第三方库。修改webpack.config.js为如下内容:

var path = require('path');

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

加载器(Loader)配置中的test值用来匹配对应的文件类型,include用来指定在哪个路径下该加载器生效。

我们先来测试一下Babel是不是已经能在Webpack里正常运作了。创建一个新的文件src/robot.js,写入以下内容:

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

export default greetings;

这一段JS代码使用了一些ES6的新特性,例如export, const以及let, 箭头函数、模板标签等。

现在我们可以试着在src/index.js里通过import导入模块:

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

之后再运行一次npm start,这时你浏览器打开的网页里应该就会多出一行字了。

很酷不是么?但截至目前我们还没有料到CSS Modules呢。在我们开始下一步之前,先删掉src/下的所有测试代码。

载入样式

再安装两个加载器,我们的项目模板差不多就准备好了:

npm i -D css-loader style-loader

css-loader用来读取CSS文件,style-loader则负责处理并将样式加载到页面中。我们先创建src/app.css来测试一下:

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

之后通过import语句导入到src/index.js文件中:

import styles from './app.css'

let element = `
  <div class="element">
    <p>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!</p>
  </div>
`

document.write(element);

等等!我们刚刚是在JS代码里导入了样式文件么?这确实是可以实现的。只需要我们再配置一下webpack.config.js里的内容:

var path = require('path');

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

然后运行npm start我们将会看到:

这时可以试着用【审查元素】检查一下文档内容,我们会发现,style-loader在<head>中写入了一个<style>标签:

现在来捋一捋刚刚到底发生了什么。我们在一个JavaScript文件里请求了另外一个CSS文件,CSS的内容被写入到了最终的网页里。所以在现实中的应用可能是在一个按钮组件中,buttons.js文件添加了buttons.css作为依赖。然后将按钮组件导入到其他JS文件中,最后组成模板,并输出HTML。这样的方式可以让我们的代码组织更合理并且很方便阅读。

为了让我们的代码更加清晰,我个人倾向于把CSS输出为单独的文件,而不是直接嵌入HTML里面。为了实现这个需求,我们需要安装一个叫做extract textWepack 插件

将载入的所有CSS模块输出为一个独立的CSS文件。这样你的样式就不至于和JS代码混淆在一起,而是生成一个独立的CSS打包文件。假如你的样式文件体积较大的话,这种方式可以加快页面加载速度,因为样式文件可以和JS文件同时载入。

通过下面这行命令来安装插件:

npm i -D extract-text-webpack-plugin

之后再对webpack.config.js配置文件稍作修改,替换CSS文件的加载器:

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

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

这样ExtractTextPlugin插件就会为我们生成styles.css文件。

这时我们就不再需要style-loader了。运行完webpack命令后,你打开/build文件夹就能够找到新生成的styles.css文件。你还需要把生成的样式文件链接手动添加到index.html中:

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

试着运行npm start,奇迹就发生啦~页面没有什么别的变化,但现在是通过外联CSS实现的。

那么接下来要如何设置才能利用CSS Modules的作用域特性呢?只需要对webpack.config.js稍作修改:

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

上面这个配置会生成长得变态的类名。Webpack通过CSS加载器来实现这个功能。

现在来修改我们的index.js,通过styles.element的方式访问CSS类:

import styles from './app.css'

let element = `
  <div class="${styles.element}">
    <p>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!</p>
  </div>
`

document.write(element);

最后再运行一次npm start,我们就可以看到一个不需要担心作用域的页面生成啦:

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

不过目前为止我们举的例子仍然很抽象,还有很多问题没有涉及到。在真正的开发工作中,我们肯定不能直接任性地通过document.write来输出页面。我们该如何架构我们的模块和文件?实现CSS Modules的功能只完成了一半的工作而已,接下来我们还得考虑如何从别的项目里迁移代码。

在下一篇教程里,我们将会讨论如何通过React来构建简单的模块,同时也会补充介绍如何在项目中应用类似Sass和PostCSS的预处理器的特性。

原文链接:https://css-tricks.com/css-modules-part-2-getting-started/,作者:Robin Rendle