每次学新东西总感觉自己是不是变笨了,看了几个博客,试着试着就跑不下去,无奈只有去看官方文档。 webpack是基于node的。先安装最新的node

1.初始化

安装node后,新建一个目录,比如html5。cmd中切到当前文件夹。
npm init -y 

这个命令会创建一个默认的package.json。它包含了项目的一些配置参数,通过它可以进行初始安装。详细参数:https://docs.npmjs.com/files/package.json

不要y参数的话,会在命令框中设置各项参数,但觉得没啥必要。

2.安装webpack

npm install webpack --save-dev
将webpack安装到当前目录。虽然npm install webpack -g 可以讲webpack安装到全局,但是容易出现一些模块找不到的错误,所以最好还是安装到当前目录下。

3.目录结构

webpack是一款模块加载各种资源并打包的工具。所以先建一个如下的目录结构:
 
app包含的开发中的js文件,一个组件,一个入口。build中就是用来存放打包之后的文件的。webpack.config.js 顾名思义用来配置webpack的。package.json就不用说了。
component.js
export default function () {
var element = document.createElement('h1');
element.innerHTML = 'Hello world';
return element;
}

component.js 是输出一个内容为h1元素。export default 是ES6语法,表示指定默认输出。import的时候不用带大括号。

index.js
import component from './component';
document.body.appendChild(component());

index.js 的作用就是引用Component模块,并在页面上输出一个h1元素。但完成这个还需要一个插件,因为目前我们还没有index.html文件。

npm install html-webpack-plugin --save-dev
html-webpack-plugin的用来生成html,将其也安装到开发目录下面。

4.设置 webpack 配置文件

我们需要通过webpack.config.js文件告诉webpack如何开始。配置文件至少需要一个入口和一个输出。多个页面就需要多个入口。node的path模块
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const PATHS = {
app: path.join(__dirname, 'app'),
build: path.join(__dirname, 'build'),
}; module.exports = {
entry: {
app: PATHS.app,
},
output: {
path: PATHS.build,
filename: '[name].js',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack demo',
}),
],
};

第一次看到这个配置文件是有点懵,主要是exports,分三个部分,一个入口,一个输出,一个插件。入口指向了app文件夹。默认会把包含"index.js"的文件作为入口。输出指定了build地址和一个文件名;[name]这儿表示占位符,可以看成webpack提供的一个变量。这个具体后面再看。而HtmlWebpackPlugin会生成一个默认的html文件。

5.打包

有了以上准备,直接输入 webpack 就能运行了。

这个输出包含了Hash(每次打包值都不同),Version,Time(耗时)。以及输出的文件信息。 这时打开build文件夹,发现多了一个app.js和index.html文件,双击index.html:

  
也可以修改下package.json
{
"name": "Html5",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"keywords": [],
"author": "", "license": "ISC",
"devDependencies": {
"html-webpack-plugin": "^2.28.0",
"webpack": "^2.2.1"
}
}

指定build。在cmd中执行npm run build 得到同样的结果

 出现helloword。再看下文件内容
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack demo</title>
</head>
<body>
<script type="text/javascript" src="app.js"></script></body>
</html>

默认引用了app.js。

6、解析

app.js

/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {}; /******/ // The require function
/******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports; /******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ }; /******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ // Flag the module as loaded
/******/ module.l = true; /******/ // Return the exports of the module
/******/ return module.exports;
/******/ } /******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules; /******/ // expose the module cache
/******/ __webpack_require__.c = installedModules; /******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; }; /******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ }; /******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ }; /******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ // __webpack_public_path__
/******/ __webpack_require__.p = ""; /******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 1);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict";
/* harmony default export */ __webpack_exports__["a"] = function () {
var element = document.createElement('h1');
element.innerHTML = 'Hello world';
return element;
}; /***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__component__ = __webpack_require__(0); document.body.appendChild(__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__component__["a" /* default */])()); /***/ })
/******/ ]);

而app.js内容比较多了。整体是一个匿名函数。

(function(module) {
})([(function (){}), function() {}])

app文件夹中的两个js文件成了这儿的两个模块。函数最开始是从__webpack_require__开始

return __webpack_require__(__webpack_require__.s = 1);

这里指定从模块1执行(赋值语句的返回值为其值)。而模块1的调用是通过__webpack_require__的这句执行的。

modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

通过call调用模块的主要作用是为了把参数传过去。

(function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__component__ = __webpack_require__(0); document.body.appendChild(__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__component__["a" /* default */])()); /***/ })

__webpack_require__ 每加载一个模块都会先去模块缓存中找,没有就新建一个module对象:

var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};

模块1中加载了模块0,

var __WEBPACK_IMPORTED_MODULE_0__component__ = __webpack_require__(0);
__WEBPACK_IMPORTED_MODULE_0__component__ 返回的是这个模块0的exports部分。而之前Component.js的默认方法定义成了
__webpack_exports__["a"] = function () {
var element = document.createElement('h1');
element.innerHTML = 'Hello world';
return element;
}

所以再模块1的定义通过"a“来获取这个方法:

document.body.appendChild(__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__component__["a" /* default */])());

这样就完整了,但这里使用了__webpack_require__.i 将原值返回。

/******/     // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };

不太明白这个i函数有什么作用。这个注释也不太明白,路过的大神希望可以指点下。

小结:

webpack通过一个立即执行的匿名函数将各个开发模块作为参数初始化,每个js文件(module)对应一个编号,每个js中export的方法或者对象有各自指定的关键字。通过这种方式将所有的模块和接口方法管理起来。然后先加载最后的一个模块(应该是引用别的模块的模块),这样进而去触发别的模块的加载,使整个js运行起来。到这基本了解了webpack的功能和部分原理,但略显复杂,且没有感受到有多大的好处。继续探索。

demo:http://files.cnblogs.com/files/stoneniqiu/webpack-ch1.zip 建议用最新的node安装,不然build后的结果可能出错。

参考:

https://survivejs.com/webpack/developing/getting-started/

https://webpack.js.org/

【webpack】-- 模块热替换

2017-03-09 11:31 by stoneniqiu, 176 阅读, 0 评论, 收藏编辑

全称是Hot Module ReplaceMent(HMR),理解成热模块替换或者模块热替换都可以吧,和.net中的热插拔一个意思,就是在运行中对程序的模块进行更新。这个功能主要是用于开发过程中,对生产环境没有任何帮助(这一点区别.net热插拔)。效果上就是界面的无刷新更新。

HMR基于WDS,style-loader可以通过它来实现无刷新更新样式。但是对于JavaScript模块就需要做一点额外的处理,怎么处理继续往下看。因为HMR是用于开发环境的,所以我们修改下配置,做两份准备。一个用于生产,一个用于开发。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); const PATHS = {
app: path.join(__dirname, 'app'),
build: path.join(__dirname, 'build'),
}; const commonConfig={
entry: {
app: PATHS.app,
},
output: {
path: PATHS.build,
filename: '[name].js',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack demo',
}),
],
} function developmentConfig(){
const config ={
devServer:{
//使能历史记录api
historyApiFallback:true,
hotOnly:true,//关闭热替换 注释掉这行就行
stats:'errors-only',
host:process.env.Host,
port:process.env.PORT,
overlay:{
errors:true,
warnings:true,
}
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
};
return Object.assign(
{},
commonConfig,
config,
{
plugins: commonConfig.plugins.concat(config.plugins),
}
);
} module.exports = function(env){
console.log("env",env);
if(env=='development'){
return developmentConfig();
}
return commonConfig;
};
这个webpack.config.js建立了两个配置,一个是commonConfig,一个是developmentConfig 两者通过env参数来区分,但这个env参数是怎么来的呢?我们看看之前的package.json中的一段:
也就是说,如果按照上面的这个配置,我们通过npm start 启动的话,进入的就是开发环境配置,如果是直接build,那么就是生产环境的方式。build方式是第一节里面讲的 直接通过npm启动webpack,这就不带WDS了。另外有了一个Object.assign语法,将配置合并。这个时候通过npm start启动,控制台打印出了两条日志。
看起来HRM已经启动了。但是此时更新一下component.js
日志显示没有东西被热更新。而且这个39,36代表的是模块Id,看起来很不直观,这里可以通过一个插件使其更符合人意。
 plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
],
这个时候再启动。

这样名称就直观了。但是我们期待的更新还是没有出来。因为需要实现一个接口
import component from './component';
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;
})
}

并修改component.js:

export default function () {
var element = document.createElement('h1');
element.innerHTML = 'Hello webpack';
return element;
}

这个时候页面更新了。每次改动页面上都会增加一个带有hot-update.js ,类似于下面这样:

webpackHotUpdate(0,{

/***/ "./app/component.js":
/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony default export */ __webpack_exports__["default"] = function () {
var element = document.createElement('h1');
element.innerHTML = 'Hello web ';
element.className='box';
return element;
}; /***/ }) })

通过webpackHotUpdate对相应模块进行更新。0表示模块的id,"./app/component.js"表示模块对应的name。结构是webpack(id,{key:function(){}})。function外带了一个括号,不知道有什么作用。webpackHotUpdate的定义是这样的:

this["webpackHotUpdate"] =
function webpackHotUpdateCallback(chunkId, moreModules) { // eslint-disable-line no-unused-vars
hotAddUpdateChunk(chunkId, moreModules);
if(parentHotUpdateCallback) parentHotUpdateCallback(chunkId, moreModules);
} ;

小结:从结构来看,一个是id,一个是对应修改的模块。但实际执行更新的是hotApply方法。热更新整个机制还是有点复杂,效果上像MVVM的那种绑定。有兴趣的可以深入研究下。不建议在生产使用HMR,会让整体文件变大,而且对生成没有什么帮助,在下一节会讲样式的加载,style-loader就是用到了HMR。但对于js模块还要写额外的代码,这让人有点不爽。

demo:http://files.cnblogs.com/files/stoneniqiu/webpack-ch3.zip

参考:

系列:

【webpack】-- 自动刷新与解析

【webpack】-- 入门与解析

【webpack】-- 自动刷新与解析

2017-02-26 23:53 by stoneniqiu, 298 阅读, 0 评论, 收藏编辑

前端需要频繁的修改js和样式,且需要根据浏览器的页面效果不断的做调整;而且往往我们的开发目录和本地发布目录不是同一个,修改之后需要发布一下;另外一点就是并不是所有的效果都可以直接双击页面就能看到,我们常常需要在本地用nginx建一个站点来观察(自己电脑上ok了才放到测试环境去)。所以如果要用手工刷新浏览器和手动(或点击)发布,还要启动站点,确实是个不小的体力活。而这三点webpack可以帮我们做到。

webpack-dev-server

webpack是通过webpack-dev-server(WDS)来实现自动刷新。WDS是一个运行在内存中的开发服务器(一个express)。启动之后,它会检测文件是否发生改变并再自动编译一次。

1.安装

npm install webpack-dev-server --save-dev

先通过npm将其安装到开发目录。安装完成之后会在node_modules/bin下找到。

2.npm启动

然后修改package.json:(基于上一节)

 "scripts": {
"start": "webpack-dev-server --env development",
"build": "webpack --env production"
}

现在就可以通过npm run start 或者 npm start来启动了。

启动之后,可以看到Project is running at http://localhost:8080 上面。打开页面

说明WDS已经帮我们自动建了一个站点.我们修改component.js ,cmd中会出现编译,页面会自动刷新。

3.直接启动

官网介绍可以直接通过下面的命令启动WDS。

webpack-dev-server --env development

但会出现webpack-dev-server --env development 不是内部命令的提示,这种问题都是环境变量的问题,将你开发的bin目录设置到环境变量中即可,比如我的目录是‘E:\Html5\node_modules\.bin’,就加上分号写在后面。

C:\Users\Administrator.9BBOFZPACSCXLG2\AppData\Roaming\npm;C:\Program Files (x86)\Microsoft VS Code\bin;E:\Html5\node_modules\.bin

4.8080端口占用

如果默认的8080端口占用,WDS会换一个。比如用nginx先发布一个。

   server{
listen 8080;
location / {
root E:/Html5/build;
index index.html index.htm;
}
}

再启动WDS:

端口切到了8081。也可以手动配置端口:

 devServer:{
//...
port: 9000
}

nodemon 自动启动

WDS是监视开发文件的,webpack.config.js改变不会引起自动启动。所以我们需要nodemon去做这件事情。

npm install nodemon --save-dev

先安装在开发目录,然后修改package.json:

 "scripts": {
"start": "nodemon --watch webpack.config.js --exec \"webpack-dev-server --env development\"",
"build": "webpack --env production"
},

等于让nodemon去监视webpack.config.js,变化了就去启动它。

这样就你可以让你的双手专心的开发了。

代理

不过有一点疑问,就是WDS这个站点的替代性,因为我们自己部署的nginx有一些api的代理。如果挂在WDS的这个默认站点上自然是无法访问的。换句话说可否给WDS配置一个刷新路径。如果文件改变去刷新指定的地址,或者让我去配个代理。既然它本身是一个http服务器,肯定也有代理的功能。搜了下果然有:https://github.com/webpack/webpack-dev-server/tree/master/examples/proxy-advanced

module.exports = {
context: __dirname,
entry: "./app.js",
devServer: {
proxy: {
"/api": {
target: "http://jsonplaceholder.typicode.com/",
changeOrigin: true,
pathRewrite: {
"^/api": ""
},
bypass: function(req) {
if(req.url === "/api/nope") {
return "/bypass.html";
}
}
}
}
}
}

即将api这个字段替换成http://jsonplaceholder.typicode.com/,并将其从原地址中删掉,这样就可以自己实现代理了。皆大欢喜!WDS是通过 http-proxy-middleware 来实现代理。更多参考:http://webpack.github.io/docs/webpack-dev-server.html#bypass-the-proxy;https://github.com/chimurai/http-proxy-middleware#options

but,这种刷新是怎么实现的呢?因为页面上没有嵌入什么别的js,去翻原码 web-dev-server/server.js中有这么一段:

Server.prototype._watch = function(path) {
const watcher = chokidar.watch(path).on("change", function() {
this.sockWrite(this.sockets, "content-changed");
}.bind(this)) this.contentBaseWatchers.push(watcher);
}

chokidar来监视文件变化,server的内部维护的有一个socket集合:

Server.prototype.sockWrite = function(sockets, type, data) {
sockets.forEach(function(sock) {
sock.write(JSON.stringify({
type: type,
data: data
}));
});
}

sock是一个sockjs对象。https://github.com/sockjs/sockjs-client,从http://localhost:8080/webpack-dev-server/页面来看,sockjs是用来通信记录日志的。

var onSocketMsg = {
hot: function() {
hot = true;
log("info", "[WDS] Hot Module Replacement enabled.");
},
invalid: function() {
log("info", "[WDS] App updated. Recompiling...");
sendMsg("Invalid");
},
hash: function(hash) {
currentHash = hash;
},
...
}

我们在看app.js,其中有一个OnSocketMsg 对象。

 

ok的时候触发一个reloadApp

function reloadApp() {
if(hot) {
log("info", "[WDS] App hot update...");
var hotEmitter = __webpack_require__("./node_modules/webpack/hot/emitter.js");
hotEmitter.emit("webpackHotUpdate", currentHash);
if(typeof self !== "undefined") {
// broadcast update to window
self.postMessage("webpackHotUpdate" + currentHash, "*");
}
} else {
log("info", "[WDS] App updated. Reloading...");
self.location.reload();
}
}

也就是说WDS先检测文件是否变化,然后通过sockjs通知到客户端,这样就实现了刷新。之前WebSocket的第三方只用过socket.io,看起来sockjs也蛮好用的。不必外带一个js,在主js里面就可以写了。

小结:效率提高的一方面是将一些机械的重复性流程或动作自动化起来。WDS和nodemon就是两个为你干活的小弟。

【webpack】-- 样式加载

2017-03-12 09:08 by stoneniqiu, 11 阅读, 0 评论, 收藏编辑

加载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/中打开

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

样式也是通过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);

得到效果:

可以看见编译成功,要注意的是,再使用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;
}

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

再看右边生成的样式,我们的样式名称已经发生了改变。回顾整个过程相当于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);

再看效果

上面这个图说明了两问题,一个是我们在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";

组合样式

我们再修改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);

看一下是什么效果:

日志打印出来的是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

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

但是我们在第三部分使用了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。

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

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

参考:

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

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

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

最新文章

  1. Linux 配置文件
  2. linux内核申请内存函数
  3. iOS中UITableView的cell点击事件不触发didSelectRowAtIndexPath(汇总)
  4. (转)基于企业级证书的IOS应用打包升级功能介绍
  5. UTF-8 BOM编码格式文件对SSI的影响
  6. installscript类型 完成时实现推荐安装其他产品的功能
  7. 华为oj---合并数组
  8. 前端基础之JavaScript
  9. Linux系统软件安装的几种方式
  10. Centos7搭建虚拟用户FTP
  11. Some elementary algorithms on discrete differential geometry(DDGSpring2016 Demos)
  12. CentOS 安装 jdk
  13. linux shell中如何往所有行尾添加内容
  14. markdown工具对比: 作业部落 vs Typora
  15. switch语句的功能是否完全可以使用if else多选择结构来代替?如果是,为什么还需要switch结构?
  16. telerik:RadGrid 分组自动展开
  17. 用C++编写发布器和订阅器节点
  18. 简单实现wc.exe软件基本功能
  19. 图像RGB格式与YUV格式互转
  20. ZOJ - 3725 Painting Storages

热门文章

  1. PAT Basic 1076
  2. STM8S与IAR程序常用错误
  3. bzoj3172 luogu3966 [TJOI2013]单词
  4. 遍历Request.QueryString
  5. FastText 介绍
  6. Leetcode 421.数组中两数的最大异或值
  7. rsa Round #71 (Div. 2 only)
  8. dp好题 玲珑杯 Expected value of the expression
  9. Docker Caffe部署
  10. mysql 游标的使用总结