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

1.初始化

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

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

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

2.安装webpack

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

3.目录结构

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

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

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

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

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

4.设置 webpack 配置文件

我们需要通过webpack.config.js文件告诉webpack如何开始。配置文件至少需要一个入口和一个输出。多个页面就需要多个入口。node的path模块
  1. const path = require('path');
  2. const HtmlWebpackPlugin = require('html-webpack-plugin');
  3.  
  4. const PATHS = {
  5. app: path.join(__dirname, 'app'),
  6. build: path.join(__dirname, 'build'),
  7. };
  8.  
  9. module.exports = {
  10. entry: {
  11. app: PATHS.app,
  12. },
  13. output: {
  14. path: PATHS.build,
  15. filename: '[name].js',
  16. },
  17. plugins: [
  18. new HtmlWebpackPlugin({
  19. title: 'Webpack demo',
  20. }),
  21. ],
  22. };

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

5.打包

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

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

  
也可以修改下package.json
  1. {
  2. "name": "Html5",
  3. "version": "1.0.0",
  4. "description": "",
  5. "main": "index.js",
  6. "scripts": {
  7. "build": "webpack"
  8. },
  9. "keywords": [],
  10. "author": "",
  11.  
  12. "license": "ISC",
  13. "devDependencies": {
  14. "html-webpack-plugin": "^2.28.0",
  15. "webpack": "^2.2.1"
  16. }
  17. }

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

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

默认引用了app.js。

6、解析

app.js

  1. /******/ (function(modules) { // webpackBootstrap
  2. /******/ // The module cache
  3. /******/ var installedModules = {};
  4.  
  5. /******/ // The require function
  6. /******/ function __webpack_require__(moduleId) {
  7.  
  8. /******/ // Check if module is in cache
  9. /******/ if(installedModules[moduleId])
  10. /******/ return installedModules[moduleId].exports;
  11.  
  12. /******/ // Create a new module (and put it into the cache)
  13. /******/ var module = installedModules[moduleId] = {
  14. /******/ i: moduleId,
  15. /******/ l: false,
  16. /******/ exports: {}
  17. /******/ };
  18.  
  19. /******/ // Execute the module function
  20. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  21.  
  22. /******/ // Flag the module as loaded
  23. /******/ module.l = true;
  24.  
  25. /******/ // Return the exports of the module
  26. /******/ return module.exports;
  27. /******/ }
  28.  
  29. /******/ // expose the modules object (__webpack_modules__)
  30. /******/ __webpack_require__.m = modules;
  31.  
  32. /******/ // expose the module cache
  33. /******/ __webpack_require__.c = installedModules;
  34.  
  35. /******/ // identity function for calling harmony imports with the correct context
  36. /******/ __webpack_require__.i = function(value) { return value; };
  37.  
  38. /******/ // define getter function for harmony exports
  39. /******/ __webpack_require__.d = function(exports, name, getter) {
  40. /******/ if(!__webpack_require__.o(exports, name)) {
  41. /******/ Object.defineProperty(exports, name, {
  42. /******/ configurable: false,
  43. /******/ enumerable: true,
  44. /******/ get: getter
  45. /******/ });
  46. /******/ }
  47. /******/ };
  48.  
  49. /******/ // getDefaultExport function for compatibility with non-harmony modules
  50. /******/ __webpack_require__.n = function(module) {
  51. /******/ var getter = module && module.__esModule ?
  52. /******/ function getDefault() { return module['default']; } :
  53. /******/ function getModuleExports() { return module; };
  54. /******/ __webpack_require__.d(getter, 'a', getter);
  55. /******/ return getter;
  56. /******/ };
  57.  
  58. /******/ // Object.prototype.hasOwnProperty.call
  59. /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  60.  
  61. /******/ // __webpack_public_path__
  62. /******/ __webpack_require__.p = "";
  63.  
  64. /******/ // Load entry module and return exports
  65. /******/ return __webpack_require__(__webpack_require__.s = 1);
  66. /******/ })
  67. /************************************************************************/
  68. /******/ ([
  69. /* 0 */
  70. /***/ (function(module, __webpack_exports__, __webpack_require__) {
  71.  
  72. "use strict";
  73. /* harmony default export */ __webpack_exports__["a"] = function () {
  74. var element = document.createElement('h1');
  75. element.innerHTML = 'Hello world';
  76. return element;
  77. };
  78.  
  79. /***/ }),
  80. /* 1 */
  81. /***/ (function(module, __webpack_exports__, __webpack_require__) {
  82.  
  83. "use strict";
  84. Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
  85. /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__component__ = __webpack_require__(0);
  86.  
  87. document.body.appendChild(__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__component__["a" /* default */])());
  88.  
  89. /***/ })
  90. /******/ ]);

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

  1. (function(module) {
  2. })([(function (){}), function() {}])

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

  1. return __webpack_require__(__webpack_require__.s = 1);

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

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

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

  1. (function(module, __webpack_exports__, __webpack_require__) {
  2.  
  3. "use strict";
  4. Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
  5. /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__component__ = __webpack_require__(0);
  6.  
  7. document.body.appendChild(__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__component__["a" /* default */])());
  8.  
  9. /***/ })

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

  1. var module = installedModules[moduleId] = {
  2. i: moduleId,
  3. l: false,
  4. exports: {}
  5. };

模块1中加载了模块0,

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

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

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

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

  1. /******/ // identity function for calling harmony imports with the correct context
  2. /******/ __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是用于开发环境的,所以我们修改下配置,做两份准备。一个用于生产,一个用于开发。

  1. const path = require('path');
  2. const HtmlWebpackPlugin = require('html-webpack-plugin');
  3. const webpack = require('webpack');
  4.  
  5. const PATHS = {
  6. app: path.join(__dirname, 'app'),
  7. build: path.join(__dirname, 'build'),
  8. };
  9.  
  10. const commonConfig={
  11. entry: {
  12. app: PATHS.app,
  13. },
  14. output: {
  15. path: PATHS.build,
  16. filename: '[name].js',
  17. },
  18. plugins: [
  19. new HtmlWebpackPlugin({
  20. title: 'Webpack demo',
  21. }),
  22. ],
  23. }
  24.  
  25. function developmentConfig(){
  26. const config ={
  27. devServer:{
  28. //使能历史记录api
  29. historyApiFallback:true,
  30. hotOnly:true,//关闭热替换 注释掉这行就行
  31. stats:'errors-only',
  32. host:process.env.Host,
  33. port:process.env.PORT,
  34. overlay:{
  35. errors:true,
  36. warnings:true,
  37. }
  38. },
  39. plugins: [
  40. new webpack.HotModuleReplacementPlugin(),
  41. ],
  42. };
  43. return Object.assign(
  44. {},
  45. commonConfig,
  46. config,
  47. {
  48. plugins: commonConfig.plugins.concat(config.plugins),
  49. }
  50. );
  51. }
  52.  
  53. module.exports = function(env){
  54. console.log("env",env);
  55. if(env=='development'){
  56. return developmentConfig();
  57. }
  58. return commonConfig;
  59. };
这个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,看起来很不直观,这里可以通过一个插件使其更符合人意。
  1. plugins: [
  2. new webpack.HotModuleReplacementPlugin(),
  3. new webpack.NamedModulesPlugin(),
  4. ],
这个时候再启动。

这样名称就直观了。但是我们期待的更新还是没有出来。因为需要实现一个接口
  1. import component from './component';
  2. let demoComponent=component();
  3. document.body.appendChild(demoComponent);
  4.  
  5. //HMR 接口
  6. if(module.hot){
  7. module.hot.accept('./component',()=>{
  8. const nextComponent=component();
  9. document.body.replaceChild(nextComponent,demoComponent);
  10. demoComponent=nextComponent;
  11. })
  12. }

并修改component.js:

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

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

  1. webpackHotUpdate(0,{
  2.  
  3. /***/ "./app/component.js":
  4. /***/ (function(module, __webpack_exports__, __webpack_require__) {
  5.  
  6. "use strict";
  7. Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
  8. /* harmony default export */ __webpack_exports__["default"] = function () {
  9. var element = document.createElement('h1');
  10. element.innerHTML = 'Hello web ';
  11. element.className='box';
  12. return element;
  13. };
  14.  
  15. /***/ })
  16.  
  17. })

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

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

小结:从结构来看,一个是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.安装

  1. npm install webpack-dev-server --save-dev

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

2.npm启动

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

  1. "scripts": {
  2. "start": "webpack-dev-server --env development",
  3. "build": "webpack --env production"
  4. }

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

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

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

3.直接启动

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

  1. webpack-dev-server --env development

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

  1. 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先发布一个。

  1. server{
  2. listen 8080;
  3. location / {
  4. root E:/Html5/build;
  5. index index.html index.htm;
  6. }
  7. }

再启动WDS:

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

  1. devServer:{
  2. //...
  3. port: 9000
  4. }

nodemon 自动启动

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

  1. npm install nodemon --save-dev

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

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

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

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

代理

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

  1. module.exports = {
  2. context: __dirname,
  3. entry: "./app.js",
  4. devServer: {
  5. proxy: {
  6. "/api": {
  7. target: "http://jsonplaceholder.typicode.com/",
  8. changeOrigin: true,
  9. pathRewrite: {
  10. "^/api": ""
  11. },
  12. bypass: function(req) {
  13. if(req.url === "/api/nope") {
  14. return "/bypass.html";
  15. }
  16. }
  17. }
  18. }
  19. }
  20. }

即将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中有这么一段:

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

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

  1. Server.prototype.sockWrite = function(sockets, type, data) {
  2. sockets.forEach(function(sock) {
  3. sock.write(JSON.stringify({
  4. type: type,
  5. data: data
  6. }));
  7. });
  8. }

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

  1. var onSocketMsg = {
  2. hot: function() {
  3. hot = true;
  4. log("info", "[WDS] Hot Module Replacement enabled.");
  5. },
  6. invalid: function() {
  7. log("info", "[WDS] App updated. Recompiling...");
  8. sendMsg("Invalid");
  9. },
  10. hash: function(hash) {
  11. currentHash = hash;
  12. },
  13. ...
  14. }

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

 

ok的时候触发一个reloadApp

  1. function reloadApp() {
  2. if(hot) {
  3. log("info", "[WDS] App hot update...");
  4. var hotEmitter = __webpack_require__("./node_modules/webpack/hot/emitter.js");
  5. hotEmitter.emit("webpackHotUpdate", currentHash);
  6. if(typeof self !== "undefined") {
  7. // broadcast update to window
  8. self.postMessage("webpackHotUpdate" + currentHash, "*");
  9. }
  10. } else {
  11. log("info", "[WDS] App updated. Reloading...");
  12. self.location.reload();
  13. }
  14. }

也就是说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

  1. npm install css-loader style-loader --save-dev

2.修改webpack.config.js

增加一个一级子节点
  1. module:{
  2. rules:[{
  3. test:/\.css$/,
  4. use: ['style-loader', 'css-loader'],
  5. }]
  6. },
test的正则会匹配.css的文件。use中的执行顺序是从右到左。loader的执行是连续的,就像管道一样,先到css-loader再到style-loader。loaders: ['style-loader', 'css-loader'] 可以理解为:styleloader(cssloader(input)) 。

3.添加样式

app/mian.css
  1. body {
  2. background: cornsilk;
  3. }

然后在index.js中引入

  1. import './main.css';

再运行npm start,在http://localhost:8080/中打开

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

样式也是通过webpackHotUpdate方法进行更新。

二、加载less

再看一下如何加载less,先安装less-loader

  1. npm install less less-loader --save-dev

再修改配置文件:

  1. module:{
  2. rules:[{
  3. test: /\.less$/,
  4.      use: ['style-loader', 'css-loader', 'less-loader'],
  5. }]
  6. },

然后建立一个less文件。less.less

  1. @base: #f938ab;
  2.  
  3. .box-shadow(@style, @c) when (iscolor(@c)) {
  4. -webkit-box-shadow: @style @c;
  5. box-shadow: @style @c;
  6. }
  7. .box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) {
  8. .box-shadow(@style, rgba(0, 0, 0, @alpha));
  9. }
  10. .box {
  11. color: saturate(@base, 5%);
  12. border-color: lighten(@base, 30%);
  13. div { .box-shadow(0 0 5px, 30%) }
  14. }
  15.  
  16. body {
  17. background: cornsilk;
  18. }

修改index.js

  1. import './less.less';
  2. import component from './component';
  3.  
  4. var ele=document.createElement("div");
  5. ele.innerHTML="this is an box";
  6. ele.className="box";
  7. document.body.appendChild(ele);
  8.  
  9. let demoComponent=component();
  10. document.body.appendChild(demoComponent);

得到效果:

可以看见编译成功,要注意的是,再使用less的时候import只能是less文件,这个时候再import main.css会报错。这一节对less就做一个简单的演示,其他样式预处理器同理,下面的内容还是继续基于css。

三、理解css作用域和css 模块

一般来说css的作用域都是全局的,我们常在母版页里面添加了多个样式文件,后面的样式文件会覆盖前面的样式文件,常常给我们的调试带来麻烦。而CSS Modules通过import引入了本地作用域。这样能够避免命名空间冲突。webpack的css-loader是支持CSS Modules的,怎么理解呢,先看几个例子。我们先在配置中开启(先关掉HMR):

  1. module:{
  2. rules:[{
  3. test:/\.css$/,
  4. use: ['style-loader', {
  5. loader: 'css-loader',
  6. options: {
  7. modules: true,//让css-loader支持Css Modules。
  8. },
  9. },],

然后定义一个新的样式(main.css):

  1. body {
  2. background: cornsilk;
  3. }
  4. .redButton {
  5. background: red;color:yellow;
  6. }

给component加一个样式,先引入main.css。

  1. import styles from './main.css';
  2. export default function () {
  3. var element = document.createElement('h1');
  4. element.className=styles.redButton;
  5. element.innerHTML = 'Hello webpack';
  6. return element;
  7. }

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

再看右边生成的样式,我们的样式名称已经发生了改变。回顾整个过程相当于main.css中的每一个类名成了一个模块,在js中可以像获取模块一样的获取。但是你可能想,为毛我不能直接给元素赋值,干嘛要import呢。这是个好问题,我们再新增一个样式

不同样式文件的同名类

other.css

  1. .redButton {
  2. background:rebeccapurple;color:snow;
  3. }

它也有一个.redbutton的类(但效果是紫色的),然后在index.js中创建一个div元素并给它添加redbutton样式。

  1. import './main.css';
  2. import styles from './other.css';
  3. import component from './component';
  4.  
  5. var ele=document.createElement("div");
  6. ele.innerHTML="this is an other button";
  7. ele.className=styles.redButton;
  8. document.body.appendChild(ele);
  9.  
  10. let demoComponent=component();
  11. document.body.appendChild(demoComponent);

再看效果

上面这个图说明了两问题,一个是我们在index.js中引入了2个样式文件,在index页面就输出了两个style,这让人有点不爽,但我们后面再解决。另外一个就是虽然两个样式文件中都有redButton这个类,但是这两者还是保持独立的。这样就避免了命名空间的相互干扰。如果你这个时候直接赋值

  1. element.className="redButton";

这样是获取不到样式的。直接对元素的样式默认是全局的。

全局样式

如果想让某个样式是全局的。可以通过:global来包住。

other.css

  1. :global(.redButton) {
  2. background:rebeccapurple;color:snow;
  3. border: 1px solid red;
  4. }

main.css

  1. :global(.redButton) {
  2. background: red;color:yellow;
  3. }

这个时候redbutton这两个样式就会合并。需要直接通过样式名来获取。

  1. element.className="redButton";

组合样式

我们再修改other.css,创建一个shadowButton 样式,内部通过composes组合redbutton类。

  1. .redButton {
  2. background:rebeccapurple;color:snow;
  3. border: 1px solid red;
  4. }
  5.  
  6. .shadowButton{
  7. composes:redButton;
  8. box-shadow: 0 0 15px black;
  9. }

修改index.js:

  1. var ele=document.createElement("div");
  2. ele.innerHTML="this is an shadowButton button";
  3. console.log(styles);
  4. ele.className=styles.shadowButton;
  5. document.body.appendChild(ele);

看一下是什么效果:

日志打印出来的是styles对象,它包含了两个类名。可以看见shadowButton是由两个类名组合而成的。div的class和下面的对应。

四、输出样式文件

css嵌在页面里面不是我们想要的,我们希望能够分离,公共的部分能够分开。extracttextplugin 可以将多个css合成一个文件,但是它不支持HMR(直接注释掉hotOnly:true)。用在生产环境挺好的
  1. npm install extract-text-webpack-plugin --save-dev

先安装extracttextplugin这个插件,然后再webpack.config.js中进行配置:

  1. const ExtractTextPlugin = require('extract-text-webpack-plugin');
  2. const extractTxtplugin = new ExtractTextPlugin({
  3. filename: '[name].[contenthash:8].css',
  4. });
  5.  
  6. const commonConfig={
  7. entry: {
  8. app: PATHS.app,
  9. },
  10. output: {
  11. path: PATHS.build,
  12. filename: '[name].js',
  13. },
  14. module:{
  15. rules:[{
  16. test:/\.css$/,
  17. use:extractTxtplugin.extract({
  18. use:'css-loader',
  19. fallback: 'style-loader',
  20. })
  21. }]},
  22. plugins: [
  23. new HtmlWebpackPlugin({
  24. title: 'Webpack demo',
  25. }),
  26. extractTxtplugin
  27. ],
  28. }

一开始看到这个配置,让人有点懵。首先看fileName,表示最后输出的文件按照这个格式'[name].[contenthash:8].css',name默认是对应的文件夹名称(这里是app),contenthash会返回特定内容的hash值,而:8表示取前8位。当然你也可以按照其他的格式写,比如直接命名:

  1. new ExtractTextPlugin('style.css')

而ExtractTextPlugin.extract本身是一个loader。fallback:'style-loader'的意思但有css没有被提取(外部的css)的时候就用style-loader来处理。注意到现在我们的index.js如下:

  1. import './main.css';
  2. import styles from './other.css';
  3. import component from './component';
  4.  
  5. var ele=document.createElement("div");
  6. ele.innerHTML="this is an box";
  7. ele.className=styles.shadowButton;
  8. document.body.appendChild(ele);
  9.  
  10. let demoComponent=component();
  11. document.body.appendChild(demoComponent);
  12.  
  13. //HMR 接口
  14. if(module.hot){
  15. module.hot.accept('./component',()=>{
  16. const nextComponent=component();
  17. document.body.replaceChild(nextComponent,demoComponent);
  18. demoComponent=nextComponent;
  19. })
  20. }

引入了两个css文件。

这个时候我们执行 npm run build

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

但是我们在第三部分使用了CSS Modules,发现other.css的样式没有打包进来。所以,我们的webpack.config.js还要修改:

  1. module:{
  2. rules:[{
  3. test:/\.css$/,
  4. use:extractTxtplugin.extract({
  5. use:[ {
  6. loader: 'css-loader',
  7. options: {
  8. modules: true,
  9. },
  10. }],
  11. fallback: 'style-loader',
  12. })
  13. }]},

再次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/
 系列:

【webpack2】-- 入门与解析的更多相关文章

  1. SoapUI简介和入门实例解析

    SoapUI简介 SoapUI是一个开源测试工具,通过soap/http来检查.调用.实现Web Service的功能/负载/符合性测试.该工具既可作为一个单独的测试软件使用,也可利用插件集成到Ecl ...

  2. 【mybatis深度历险系列】mybatis的框架原理+入门程序解析

    在前面的博文中,小编介绍了springmvc的相关知识点,在今天这篇博文中,小编将介绍一下mybatis的框架原理,以及mybatis的入门程序,实现用户的增删改查,她有什么优缺点以及mybatis和 ...

  3. Spring源码入门——DefaultBeanNameGenerator解析 转发 https://www.cnblogs.com/jason0529/p/5272265.html

    Spring源码入门——DefaultBeanNameGenerator解析   我们知道在spring中每个bean都要有一个id或者name标示每个唯一的bean,在xml中定义一个bean可以指 ...

  4. xml入门与解析

    xml入门与解析 1.xml基础知识 xml:可扩展的标签语言,标签自定义. 作用:存储数据.(配置文件) 书写规范: 1.区分大小写 2.应该有一个根标签 3.标签必须关闭 <xx>&l ...

  5. webpack2入门概念

    webpack是一种JavaScript应用模块化打包工具,它配置起来简单易上手,因此很多企业工程化代码都使用它来打包.在具体介绍如何使用webpack之前,先来介绍下webpack的四个核心概念. ...

  6. SpringBoot快速入门(解析+入门案例源码实现)

    这里写目录标题 SpringBoot入门 一.SpringBoot 概念 二.JavaConfig 入门 1. JavaConfig 概念 2. 项目准备 三.常用注解 四.SpringBoot 入门 ...

  7. Spring源码入门——XmlBeanDefinitionReader解析

    接上篇[] ,我们看到BeanDefinitionReader解决的是从资源文件(xml,propert)到BeanDefinition集合的过程.所以BeanDefinitionReader接口有两 ...

  8. Spring源码入门——AnnotationBeanNameGenerator解析

    ---恢复内容开始--- 接上篇,上篇解析了DefaultBeanGenerator生成bean name的过程(http://www.cnblogs.com/jason0529/p/5272265. ...

  9. webpack入门与解析(一)

    每次学新东西总感觉自己是不是变笨了,看了几个博客,试着试着就跑不下去,无奈只有去看官方文档. webpack是基于node的.先安装最新的node. 1.初始化 安装node后,新建一个目录,比如ht ...

随机推荐

  1. loj2026 「JLOI / SHOI2016」成绩比较

    orz #include <iostream> #include <cstdio> using namespace std; typedef long long ll; int ...

  2. 令人惊叹的Npm工具包

    1.http-server (简单搭建http服务器) 2.json-server (JSON服务器,快速搭建resful api接口) 3.cssnano (css多功能优化工具) PS:比uncs ...

  3. 通用的前端js代码

    1.判断是否移动设备的浏览器,是否允许触摸事件.(响应式网页) if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i. ...

  4. PHP中define()和const定义常量的区别

    在PHP中可以通过define()和const两种方式定义常量可是在开发中我们应该什么时候用define()定义常量,什么时候用const定义常量? 这两种方式定义常量的主要区别是什么? 从5.3版本 ...

  5. [adb 学习篇] adb pull

    adb pull   E:\uitest\testcase\CaseDemo\testcase\3dmark\3DMarkAndroid   /sdcard/3DMarkAndroid 假设:  E: ...

  6. Hibernate的简单封装Session(方便调用)

    因为每次用增删改查时都需要用到hibernate的配置来生成session工厂进而生成session,比较麻烦,所以我们直接封装一个可以调用的类,需要的时候只需要调用即可. 新建一个Hibernate ...

  7. volatile的用法

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  8. Cannot open include file: 'initializer_list': No such file or directory

    Cannot open include file: 'initializer_list': No such file or directory今天使用VS2012编译一个项目的时候,遇到了这个问题,上 ...

  9. [暑假集训--数位dp]hdu5787 K-wolf Number

    Alice thinks an integer x is a K-wolf number, if every K adjacent digits in decimal representation o ...

  10. 解决 Springboot中Interceptor拦截器中依赖注入失败

    问题: 在Springboot拦截器Interceptor中使用@Resource依赖注入时,发现运行的时候被注解的对象居然是null,没被注入进去 原配置为: @Configurationpubli ...