• 【webpack2】-- 入门与解析


    每次学新东西总感觉自己是不是变笨了,看了几个博客,试着试着就跑不下去,无奈只有去看官方文档。 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 ode_modules.bin’,就加上分号写在后面。

    C:UsersAdministrator.9BBOFZPACSCXLG2AppDataRoaming
    pm;C:Program Files (x86)Microsoft VS Codein;E:Html5
    ode_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 对象。

     View Code

    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/
     系列:
  • 相关阅读:
    MYSQL中replace into的用法以及与inset into的区别
    怎么安装phpcms?PHPCMS V9安装图文教程
    Yii 框架生成缩略图
    怎么让普通用户使用root权限执行用户命令
    自学Linux命令的四种方法
    最完整PHP.INI中文版
    前端chrome浏览器调试
    phpstorm快捷键记录
    客户关系管理
    Subquery returns more than 1 row
  • 原文地址:https://www.cnblogs.com/libin-1/p/6537051.html
Copyright © 2020-2023  润新知