该篇为翻译文章:原文为:Getting Started with CSS Modules

该系列文章目录:

第一部分: CSS Modules是什么东西,我们为什么需要它?-译文(原文)
第二部分: 如何使用CSS Modules (当前)
第三部分: CSS Modules和React-译文(原文)

首先,要使用CSS Modules并不是只有某一种方法才能实现,但是不管是什么方法,都是通过一些构建工具把CSS和JS很好的结合起来。在这篇文章当中,我们将介绍其中一种方法。看完这篇文章后,相信你就可以利用CSS Modules来写一些东西了。

在我的项目经历当中,有一个要求就是:项目当中的CSS不能依赖JS,所以我们要在项目部署上线前把所有的HTML和CSS通过构建工具编译好。这一次,我们将使用Webpack(一个构建和模块打包工具)。而在下篇文章当中,我们将针对实际项目的需求对我们的代码做出相应的优化并最终生成浏览器能识别的静态HTML。

安装Webpack

安装了NPM和node之后,我们在一个空白文件夹当中运行以下这段命令:

npm init --y

运行之后,将会生成一个 package.json文件,并且文件里有一些默认的参数选项。这里面列的就是我们所依赖到的一些node模块,当别人需要获取这里所列出来的模块时,那么它只要得到这个文件,然后运行npm install就会自动进行安装了。

Webpack将会对我们的项目进行编译操作。它会在我们项目开发中一直监听CSS,JS,HTML这些文件。但问题来了,到底什么是Webpack呢?Maxime Fabre认为它是一个构建工具模块打包工具

我觉得Webpack即是构建,同时也是模块打包工具,它把这两项工作在同一时间一起完成了。Webpack不会动你的静态资源文件,它会分别去处理你的模块,它认为你的静态资源是属于模块的,这些静态资源是需要修改、导入、维护等操作的,但最终会打成一个包。

如果你觉得上面的定义听起来有点奇怪,不用担心,我们将会慢慢弄明白它到底是干嘛用的。

首先我们需要在全局环境当中安装Webpack,我们在命令行当中输入:

npm install webpack -g

全局的安装好之后,接下来还要在项目环境当中也装一下:

npm i -D webpack

我们创建一个index.js放到/src目录下。通常,我喜欢把所有的静态资源(比如图片、字体、样式、HTML文件)都放到/src目录下。然后让所有构建工具生成的文件都放到/build目录下。当我把/build目录完全删掉是没有任何影响的,因为我们只需要执行一条命令,Webpack就会重新根据/src生成/build。在我们要说的这个案例当中,我们将会让Webpack来监视/src当中所有的文件,然后执行一个特定的操作,所有的东西都编译生成到/build当中。

我们可以在/src目录下再创建一个空的alert.js文件(一会儿会用到)。另外,我们还需要在我们的项目根目录下创建一个webpack.config.js文件(Webpack的配制文件),现在我们的项目结构应该是这样的:

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");

然后在src/alert.js当中:

alert("LOUD NOISES");

现在我们在项目根目录创建一个index.html文件,然后在页面的“前引入build/bundle.js:

<!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属性,首先在package.json当中找到:

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

上面的这一段是npm默认加上去的,下面我们将上面的代码替换成下面这段。:

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

无论我们什么时候运行npm start,webpack命令将会自动运行,并且会自动在浏览器当中打开我们的首页。

webpack_index

It Works!如果我们现在删掉alert.js然后再运行npm start则会得到一个错误:
2

如果是引入一个模块失败,Webpack就会报出上面那个错误。上面在index.js引入alert.js只是为了给大家演示一下,现在我们删掉它,然后继续来看Webpack。

第一个Webpack加载器

“加载器”在Webpack当中是非常重要的。Maxime Fabre总结”加载器”就是

加载器就是一些小插件,这些插件的功能就是针对不同的文件做不同的事情。

在Maxime的文章中,他添加了Babel加载器,Babel能够让我们放心大胆的使用ES2015。因此,我们可以使用import来代替之前使用Common.js规范当中的require来加载相应模块。使用Babel,我们还可以使用类,箭头函数等等。
接下来,我们就来安装babel加载器:

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

在项目根目录下的.babelrc文件当中,我们可以设置JS的语法版本:

{
  "presets": ["es2015"]
}

现在我们可以使用Babel来写我们自己的JS文件了。另外,以后我们还会安装其它的依赖模块,这些模块可能还会有它们自己的语法,我们不能让这些语法之间产生混乱,这时候加载器就起了作用。我们打开webpack.config.js,然后把里面的代码替换成:

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

loaders数组当中的test属性是告诉Webpack我们需要监测什么类型的文件,include属性则是告诉Webpack去哪里找文件。
现在让我们来测试一下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;

上面的代码当中用了好多ES2015中的新特性,像export,const,let,箭头函数和模板字符串。

现在我们可以在src/index.js当中导入上面的src/robot.js模块:

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

最后,我们运行npm start,如果浏览器自动打开,然后页面当中显示“Affirmative, Dave. I read you but I’m sorry, I’m afraid I can’t do that.”,那么说明Babel已经起作用了。
到这里,我们已经成功一小半了!但是到目前为止,还没有说到CSS 模块,上面的例子其实是JS模块,下面我们就正式进入CSS Modules。

加载样式

要开始使用CSS Modules,先要加载 css-loaderstyle-loader:

npm i -D css-loader style-loader

css-loader用来处理所有的CSS文件,style-loader则是将这些样式和HTML结合到一起。我们来写一点CSS到src/app.css中:

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

然后在src/index.jsimport``src/app.css

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文件当中引入了CSS。在我们看效果之前再来修改一下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则会看到:
3

我们用开发者工具可以看到 style-loader已经把样式文件里的内容添加到了HTML当中,放到了<style>标签当中:
4

我们使用JS引入了CSS文件,然后样式被自动嵌入到了网页当中。下面我们要做一些实际的例子,新建一个buttons.jsbuttons.css(这个可以看做是单独的按钮模块),并在buttons.js导入buttons.css,然后我们可以在其它的文件当中导入buttons.js。这样,我们的项目就会组件化,模块化。
我们要尽量让项目结构变的清晰,所以我们需要把不同功能的CSS放到不同的文件当中去。我们可以通过Webpack的一个插件extract text来进行实现。可以通过下面这段npm命令来进行安装:

npm i -D extract-text-webpack-plugin

现在我们需要再次修改一下webpack.config.js文件:

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插件将会为我们创建一个style.css文件。

你可能发现了,我们已经把style-loader加载器删掉了。因为,我们现在不需要把样式编译到HTML当中。现在,如果我们打开/builld目录,我们应该会看到styles.css文件已经被创建了。现在我们在<head>当中载入样式文件即可:

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

现在,npm start,样式就会出现在它该在的地方了。
现在,我们的页面可以正常访问了,但是,下面该如何维护class名来充分的利用本地作用域给我们带来的好处呢?再来改一下webpack.config.js:

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

这样,我们再次运行时,Webpack中的CSS加载器会在class名后面就会加上一些随机生成的字符串。这样,才是实现了真正的CSS Modules。
现在我们把index.js中的styles.element改一下:

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);

再次重新构建一下项目,你就会发现:

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

虽然效果实现了,但是仍然有很多问题。上面的代码当中是有很多不合理的地方的,比如用了document.write把内容写到页面上。还有,我们如何管理文件和模块呢?在下篇文章当中,我们需要想办法来处理这些问题。
下一次,我们将使用React来帮助我们来生成组件,如何将几个模板组合生成最终的静态页面。另外,我们还将会看到如何把Sass或PostCSS这类工具加到项目构建当中来。