webpack_webpack4 - CSDN
  • 在本课程中,首先介绍了webpack打包工具的基础知识,安装过程,然后介绍工具如何打包第三方插件、打包样式的方法,后,介绍打包工具的配置,并详细介绍如何将项目部署到服务端的全部流程。
  • Webpack的基本使用

    2020-06-07 18:54:47
    一、什么是Webpack Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。 从图中我们可以看出,Webpack 可以将多种静态资源 js、css、less...

    一、什么是Webpack

    Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

    从图中我们可以看出,Webpack 可以将多种静态资源 js、css、less 转换成一个静态文件,减少了页面的请求。

    在这里插入图片描述

    二、Webpack安装

    1、全局安装

    npm install -g webpack webpack-cli
    

    2、安装后查看版本号

    webpack -v
    

    三、初始化项目

    1、创建webpack文件夹

    进入webpack目录,执行命令

    npm init -y
    

    2、创建src文件夹

    3、src下创建common.js

    exports.info = function (str) {
        document.write(str);
    }
    

    4、src下创建utils.js

    exports.add = function (a, b) {
        return a + b;
    }
    

    5、src下创建main.js

    const common = require('./common');
    const utils = require('./utils');
    common.info('Hello world!' + utils.add(100, 200));
    

    四、JS打包

    1、webpack目录下创建配置文件webpack.config.js

    以下配置的意思是:读取当前项目目录下src文件夹中的main.js(入口文件)内容,分析资源依赖,把相关的js文件打包,打包后的文件放入当前目录的dist文件夹下,打包后的js文件名为bundle.js

    const path = require("path"); //Node.js内置模块
    module.exports = {
        entry: './src/main.js', //配置入口文件
        output: {
       path: path.resolve(__dirname, './dist'), //输出路径,__dirname:当前文件所在路径
            filename: 'bundle.js' //输出文件
        }
    }
    

    2、命令行执行编译命令

    webpack #有黄色警告
    webpack --mode=development #没有警告
    #执行后查看bundle.js 里面包含了上面两个js文件的内容并惊醒了代码压缩
    

    也可以配置项目的npm运行命令,修改package.json文件

    "scripts": {
        //...,
        "dev": "webpack --mode=development"
     }
    

    运行npm命令执行打包

    npm run dev
    

    3、webpack目录下创建index.html

    引用bundle.js

    <body>
        <script src="dist/bundle.js"></script>
    </body>
    

    4、浏览器中查看index.html

    五、CSS打包

    1、安装style-loader和 css-loader

    Webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。

    Loader 可以理解为是模块和资源的转换器。

    首先我们需要安装相关Loader插件,css-loader 是将 css 装载到 javascript;style-loader 是让 javascript 认识css

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

    2、修改webpack.config.js

    const path = require("path"); //Node.js内置模块
    module.exports = {
        //...,
        output:{},
        module: {
            rules: [  
                {  
                    test: /\.css$/,    //打包规则应用到以css结尾的文件上
                    use: ['style-loader', 'css-loader']
                }  
            ]  
        }
    }
    

    3、在src文件夹创建style.css

    body{
        background:pink;
    }
    

    4、修改main.js

    在第一行引入style.css

    require('./style.css');
    

    5、浏览器中查看index.html

    看看背景是不是变成粉色啦?

    展开全文
  • 博主在这里就不详细介绍webpack来源以及作用了, 本篇博文面向新手主要说明如何配置webpack, 以及webpack的使用方法, 直到创建出一个合理的属于自己webpack项目.流程webpack安装 Step 1: 首先安装Node.js, 可以去Node...

    博主在这里就不详细介绍webpack来源以及作用了, 本篇博文面向新手主要说明如何配置webpack, 以及webpack的使用方法, 直到创建出一个合理的属于自己webpack项目;

    注: 此篇博客适用于webpack2.x, 对于4.x版本变化还挺大的, 过一阵子会再写一篇, 点关注不走丢哦

    流程

    webpack安装

    • Step 1: 首先安装Node.js, 可以去Node.js官网下载.

    • Step 2: 在Git或者cmd中输入下面这段代码, 通过全局先将webpack指令安装进电脑中

    npm install webpack -g
    
    • Step 3: 使用Git Bash here 或者 cmd cd命令使当前目录转到当前项目的目录下, 然后输入下面这段命令
      npm init
      这里写图片描述
      接下来会弹出一些信息, 就是一些项目名和一些参数的描述, 可以全部按回车使用默认信息, 完成之后项目文件夹下会生成一个package.json的文件
      这里写图片描述
      这样webpack就安装完成了.




    webpack配置

    • Step1: 创建项目文件夹, 名字自起, 但路径名不要包含中文, 以及项目名也不要叫"webpack", 并且不要包含大写字母.
      例:这里写图片描述

    • Step2: 接下来创建并编写配置文件. 首先我们先介绍几个配置文件的参数.

      • entry: 是 页面入口文件配置 (html文件引入唯一的js 文件)
      • output:对应输出项配置
        • path :入口文件最终要输出到哪里,
        • filename:输出文件的名称
        • publicPath:公共资源路径
    • Step3: 在你的项目目录下创建webpack.config.js配置文件, 通过这个文件进行webpack的配置, 并且还要创建一些路径保存基本文件. 例如:
      这里写图片描述
      src文件夹
      这里写图片描述

    • Step4: 在src的js下创建一个入口文件, 我创建的叫做entry.js, 在项目目录再创建一个index.html用来调试使用. 编写webpack.config.js文件,

      //webpack.config.js
      module.exports = {
          entry : './src/js/entry.js',//入口文件
          output : {//输出文件
              filename : 'index.js',//输出文件名
              path : __dirname + '/out'//输出文件路径
          },
      

    }
    ```
    我们随便在index.html和入口文件entry.js写点什么看看是否成功配置,

    ```
    //index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>ss</title>
    </head>
    <body>
    111
        <script src="./out/index.js"></script>//注意在这里引入的是打包完成的js文件
    </body>
    </html>
    ```
    
    
    ```javascript
    //entry.js
    console.log('1234');
    ```
    之后使用Git Bash here 或者 cmd cd命令使目录为当前项目目录, 输入**webpack**或者**webpack -w**命令, 查看index.html是否成功console出1234;
    

    webpack 和 webpack -w 区别
    webpack -w可以对项目打包并且实时监控, 当前配置文件下的文件如果发生更改时重新打包, 但如果webpack的配置文件即webpack.config.js更改时还是要通过webpack进行打包.(退出webpack -w 操作 ctrl+c)


    ### webpack loader加载器

    接下来我们继续配置loader, 通过加载器处理文件:比如 sass less 等, 告知 webpack 每一种文件都需要使用什么加载器来处理。

    • **Step1: **为了方便我们先统一把所有的包都先下载下来, 下面再慢慢解释.

      npm install babel-loader babel babel-core css-loader style-loader  url-loader file-loader less-loader less  --save-dev 
      
      

    - **Step2: **下载完成后, 我们修改webpack.config.js文件, 将加载器引入.
    ```
    module.exports = {
        entry :  './src/js/entry.js',
        output : {
            filename : 'index.js',
            path : __dirname + '/out'
        },
        module : {
            rules: [
                {test: /.js$/, use: ['babel-loader']},
                {test: /.css$/, use: ['style-loader', 'css-loader']},/*解析css, 并把css添加到html的style标签里*/
                //{test: /.css$/, use: ExtractTextPlugin.extract({fallback: 'style-loader',use: 'css-loader'})},/*解析css, 并把css变成文件通过link标签引入*/
                {test: /.(jpg|png|gif|svg)$/, use: ['url-loader?limit=8192&name=./[name].[ext]']},/*解析图片*/
                {test: /.less$/, use: ['style-loader', 'css-loader', 'less-loader']}/*解析less, 把less解析成浏览器可以识别的css语言*/
            ]
        },
        }
    ```
    <br>
    
    • **Step3: **接下来我们先测试css, 我们在项目文件夹下的src文件夹下创建index.css. 随便写一点属性.

      //index.css
      .demo1 {
          width: 100px;
          height: 100px;
          background: red;
      }
      .demo2 {
          width: 200px;
          height: 200px;
          background: orange;
      }
      
      //index.html
      <!DOCTYPE html>
      
    text
    ``` 因为在webpack中所有文件都是模块, 所以必须要将css引入. 在刚刚的entry.js中添加如下代码.
    ```javascript
    //entry.js
    require('../css/index.css');//引入css文件
    console.log("1234");
    ```
    
    打包webpack一下看看效果
    ![这里写图片描述](https://img-blog.csdn.net/20170506175719289?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY19raXRl/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)<br>
    
    • **Step4: **当有多个js文件时, 如何进行引入呢? 接下来我们做一个简单小功能来说明这个问题, 让我们点击方块的时候, 方块变大.

      接下来在src的js文件夹下创建一个基本的工具tool.js (很多小的问题都被我扩大化了, 只是为了说明问题不一定适用)

      //tool.js
      var tool = {//获取DOM元素
          getDom: function(className) {
              return document.getElementsByClassName(className)[0];
          }
      }
      
      module.exports = tool;//模块出口
      

      src的js下创建一个demo1.js文件, demo2.js同理

      var obj = require('./tool.js');
          var demo1 = {
          init: function() {
              this.bindEvent();
          },
          bindEvent: function() {
              //var demo1 = document.getElementsByClassName('demo1')[0];
              var demo1 = obj.getDom('demo1');
              demo1.onclick = this.changeStyle;
          },
          changeStyle: function() {
              this.style.width = '200px';
              this.style.height = '200px';
              this.style.background = 'green';
              console.log('1');
          }
      }
      
      module.exports = demo1;
      

      修改入口文件entry.js

      require('../css/index.css');
      
      var demo1 = require('../js/demo1.js');
      var demo2 = require('../js/demo2.js');
      
          demo1.init();
          demo2.init();
      

      webpack一下, 看看效果
      这里写图片描述



    关于图片的打包

    • **Step1: **在img文件夹下随便找一个小一点的图片放进去.

    • **Step2: **修改entry.js

      require('../css/index.css');
      
      var demo1 = require('../js/demo1.js');
      var demo2 = require('../js/demo2.js');
      
          demo1.init();
          demo2.init();
      
      var oImg = new Image();
      oImg.src = require('../img/1.gif');//当成模块引入图片
      document.body.appendChild(oImg);
      

      由于我们引入的是静态资源, 在webpack.config.js中修改一下

      module.exports = {
      	    entry :  './src/js/entry.js',
      	    output : {
      	        filename : 'index.js',
      	        publicPath: __dirname + '/out',//添加静态资源, 否则会出现路径错误
      	        path : __dirname + '/out'
      	    },
      	    module : {
      	        rules: [
      	            {test: /.js$/, use: ['babel-loader']},
      	            {test: /.css$/, use: ['style-loader', 'css-loader']},/*解析css, 并把css添加到html的style标签里*/
      	            //{test: /.css$/, use: ExtractTextPlugin.extract({fallback: 'style-loader',use: 'css-loader'})},/*解析css, 并把css变成文件通过link标签引入*/
      	            {test: /.(jpg|png|gif|svg)$/, use: ['url-loader?limit=8192&name=./[name].[ext]']},/*解析图片*/
      	            {test: /.less$/, use: ['style-loader', 'css-loader', 'less-loader']}/*解析less, 把less解析成浏览器可以识别的css语言*/
      	        ]
      	    },
      }
      

      大家自己webpack看看效果



    webpack进阶设定

    我们在项目中有多个html文件时怎么处理呢?, 接下来我们重新设定配置文件, webpack.config.js

    	module.exports = {
    		    entry : {index1: './src/js/entry.js', index2: './src/js/entry2.js'},
    	    output : {
    		        filename : '[name].js',//这样就可以生成两个js文件, 名字分别为index1.js, 和index2.js
    		        publicPath: __dirname + '/out',//添加静态资源, 否则会出现路径错误
    		        path : __dirname + '/out'
    		    },
    		    module : {
    		        rules: [
    		            {test: /.js$/, use: ['babel-loader']},
    		            {test: /.css$/, use: ['style-loader', 'css-loader']},/*解析css, 并把css添加到html的style标签里*/
    		            //{test: /.css$/, use: ExtractTextPlugin.extract({fallback: 'style-loader',use: 'css-loader'})},/*解析css, 并把css变成文件通过link标签引入*/
    		            {test: /.(jpg|png|gif|svg)$/, use: ['url-loader?limit=8192&name=./[name].[ext]']},/*解析图片*/
    		            {test: /.less$/, use: ['style-loader', 'css-loader', 'less-loader']}/*解析less, 把less解析成浏览器可以识别的css语言*/
    		        ]
    		    },
    	}
    


    webpack插件使用

    首先我们由于要使用webpack插件, 因此我们要重新下载一下webpack包, 将目录定位到当前项目目录, 输入npm install webpack
    之后我们修改webpack.config.js, 将下面代码放到配置文件开头. 这样就可以使用插件了

    var webpack = require('webpack');
    

    将插件信息写到配置文件里

    var webpack = require('webpack');
    var uglifyPlugin = new webpack.optimize.UglifyJsPlugin({minimize: true});//代码压缩
    
    var CommonsChunkPlugin = new webpack.optimize.CommonsChunkPlugin('common');//把公共模块提取出来, 并取名为'common'(名字自起), webpack之后再out文件夹下生成common.js, 测试时记得引入提取出来的公共模块js文件
    
    var ExtractTextPlugin = require("extract-text-webpack-plugin");//将css独立引入变成link标签形式, 使用该插件需要独立下载'npm install extract-text-webpack-plugin --save-dev', 同时下面的rules也必须更改
    
    var providePlugin = new webpack.ProvidePlugin({$: 'jquery', jQuery: 'jquery', 'window.jQuery': 'jquery'});//引入jquery
    
    
    
    module.exports = {
        entry : {index: './src/js/entry.js', index2: './src/js/entry2.js'},
        output : {
            filename : '[name].js',
            publicPath: __dirname + '/out',
            path : __dirname + '/out'
        },
        module : {
            rules: [
                {test: /.js$/, use: ['babel-loader']},
                //{test: /.css$/, use: ['style-loader', 'css-loader']},
                {test: /.css$/, use: ExtractTextPlugin.extract({fallback: 'style-loader',use: 'css-loader'})},
                {test: /.(jpg|png|gif|svg)$/, use: ['url-loader?limit=8192&name=./[name].[ext]']},
                {test: /.less$/, use: ['style-loader', 'css-loader', 'less-loader']}
            ]
        },
        plugins: [uglifyPlugin, CommonsChunkPlugin, new ExtractTextPlugin('[name].css'),providePlugin]//插件集合
    }
    


    webpack服务器

    首先先定位目录输入命令 npm install webpack-dev-server -g, 修改webpack.config.js文件

    publicPath: 'http://localhost:8080/out',
    

    html文件所引用的目录也要更改:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>text</title>
         <link rel="stylesheet" href="http://localhost:8080/out/index.css">
    </head>
    <body>
        <a href="http://localhost:8080/index2.html">11</a>
        <div class="demo1"></div>
        <div class="demo2"></div>
        <script src="http://localhost:8080/out/common.js"></script>
        <script src="http://localhost:8080/out/index.js"></script>
    </body>
    </html>
    

    webpack-dev-server一下看看效果

    小工具: webpace-dev-server --devtool eval-source-map --progess --colors打包时输入这一行可以直接找到文件如果出错时的位置

    小知识点: 上面这一句话如果觉得太长可以将

    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "xuan": "webpack-dev-server --devtool eval-source-map --progress --colors"
      },
    

    这一段代码放到项目目录下的package.json里, 这样调试时如果输入npm run xuan就等于输入那一长串代码

    展开全文
  • 一、全面理解webpack 1、什么是 webpack?  webpack是近期最火的一款模块加载器兼打包工具,它能把各种资源,例如JS(含JSX)、coffee、样式(含less/sass)、图片等都作为模块来使用和处理,它能有Grunt或Gulp...

    一、全面理解webpack

    1、什么是 webpack?

           webpack是近期最火的一款模块加载器兼打包工具,它能把各种资源,例如JS(含JSX)、coffee、样式(含less/sass)、图片等都作为模块来使用和处理,它能有Grunt或Gulp所有基本功能。webpack的官网是 https://webpack.github.io/ ,文档地址是https://webpack.github.io/docs,官网对webpack的定义是MODULE BUNDLER,他的目的就是把有依赖关系的各种文件打包成一系列的静态资源。 请看下图:

    2、webpack 的优势

    其优势主要可以归类为如下几个:

    1. webpack 是以 commonJS 的形式来书写脚本滴,但对 AMD/CMD 的支持也很全面,方便旧项目进行代码迁移。
    2. 支持很多模块加载器的调用,可以使模块加载器灵活定制,比如babel-loader加载器,该加载器能使我们使用ES6的语法来编写代码;less-loader加载器,可以将less编译成css文件;
    3. 开发便捷,能替代部分 grunt/gulp 的工作,比如打包、压缩混淆、图片转base64等。
    4. 可以通过配置打包成多个文件,有效的利用浏览器的缓存功能提升性能。

    3、wepback它的目标是是什么?

    webpack它能将依赖的模块转化成可以代表这些包的静态文件

    • 将依赖的模块分片化,并且按需加载
    • 解决大型项目初始化加载慢的问题
    • 每一个静态文件都可以看成一个模块
    • 可以整合第三方库
    • 能够在大型项目中运用
    • 可以自定义切割模块的方式

    4、webpack较之其他类似工具有什么不同?

    • 有同步和异步两种不同的加载方式
    • Loader,加载器可以将其他资源整合到JS文件中,通过这种方式,可以讲所有的源文件形成一个模块
    • 优秀的语法分析能力,支持 CommonJs AMD 规范
    • 有丰富的开源插件库,可以根据自己的需求自定义webpack的配置

    5、webpack为什么要将所有资源放在一个文件里面?

    我们知道,对于浏览器来说,加载的资源越少,响应的速度也就越快,所以有时候我们为了优化浏览器的性能,会尽可能的将资源合并到一个主文件app.js里面。但是这导致的很大的缺点:

    • 当你的项目十分庞大的时候,不同的页面不能做到按需加载,而是将所有的资源一并加载,耗费时间长,性能降低。
    • 会导致依赖库之间关系的混乱,特别是大型项目时,会变得难以维护和跟踪。比如:哪些文件是需要A模块加载完后才能执行的?哪些页面会受到多个样式表同时影响的? 等许多问题。

    而webpack可以很好的解决以上缺点,因为它是一个十分聪明的模块打包系统,当你正确配置后,它会比你想象中的更强大,更优秀。

    二、开启wbpack之旅

    安装步骤如下:

    1、生成package.json文件;

    先装好node和npm,因为webpack是一个基于node的项目。然后首先我们需要在根目录下生成package.json文件,需要进入项目文件内根目录下执行如下命令:npm init

    如上通过一问一答的方式后会在根目录下生成package.json文件,如下所示:

    2 . 通过全局安装webpack

    执行命令如下:npm install -g webpack 如下所示:

    在c盘下会生成node_modules文件夹中会包含webpack,此时此刻我们可以使用webpack命令了;

    在常规项目中把webpack依赖加入到package.json

    npm init npm install webpack --save

    更详尽的安装方法个可以参考webpack安装

    3. 配置webpack

    每个目录下都必须有一个webpack.config.js,它的作用就好比Gulpfile.js、或者 Gruntfile.js,就是一个项目配置,告诉webpack需要做什么。

    首先先贴上一个比较完整的webpack.config.js的代码,再详细介绍:

    //详细的webpack.config.js结构分析:
    var path = require('path');
    var webpack = require('webpack');
    var HtmlWebpackPlugin = require('html-webpack-plugin');
    var TransferWebpackPlugin = require('transfer-webpack-plugin');
     
    module.exports = {
        devtool: 'source-map',//由于打包后的代码是合并以后的代码,不利于排错和定位,只需要在config中添加,这样出错以后就会采用source-map的形式直接显示你出错代码的位置。
        //noParse:[/jquery/],//表示跳过jquery,不对其进行编译,这样可以提高打包的速度
        //页面入口文件配置
        entry: {
            page1: "./src/index.js",
            //page2: ["./src/index.js", "./src/main.js"],支持数组形式,将加载数组中的所有模块,但以最后一个模块作为输出
        },
        //入口文件输出配置
        output: {
            path: "dist/js/page",
            filename: "[name].bundle.js",// page1.bundle.js 和 page2.bundle.js,并存放到 ./dist/js/page 文件夹下。
            publicPath: "/dist/"    //网站运行时的访问路径。
        },
        resolveLoader: {
            //指定默认的loader路径,否则依赖走到上游会找不到loader
            root: path.join(__dirname, 'node_modules'),
            alias: {//给自己写的loader设置别名
                "seajs-loader": path.resolve( __dirname, "./web_modules/seajs-loader.js" )
            }
        },
        //新建一个开发服务器,并且当代码更新的时候自动刷新浏览器。
        devServer: {
            historyApiFallback: true,
            noInfo: true,
            hot: true,
            inline: true,
            progress: true,
            port:9090 //端口你可以自定义
        },
        module: {
            // module.loaders 是最关键的一块配置。它告知 webpack每一种文件都需要使用什么加载器来处理:
            loaders: [
            { test: /\.css$/, loader: 'style-loader!css-loader' },//.css 文件使用 style-loader 和 css-loader 来处理.
            //{ test: /\.css$/, loader: 'style!css' },其他写法1、"-loader"其实是可以省略不写的,多个loader之间用“!”连接起来。
            //{ test: /\.css$/, loaders: ["style", "css"] },其他写法2、用loaders数组形式;
            //.scss 文件使用 style-loader、css-loader 和 sass-loader 来编译处理。
            //在chrome中我们通过sourcemap可以直接调试less、sass源文件文件
            { test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
            { test: /\.less$/, loader: 'style!css!less?sourceMap'},//.less 文件使用 style-loader、css-loader 和 less-loader 来编译处理
            //.js 文件使用babel-loader来编译处理,设置exclude用来排除node_modules这个文件夹中的代码
            { test: /\.js$/, loader: 'babel!jsx',exclude: /node_modules/ }, 
            { test: /\.jsx$/, loader: "jsx-loader?harmony" },//.jsx 文件使用 jsx-loader 来编译处理
            { test: /\.json$/,loader: 'json'},
            //{ test: /\.(png|jpg|jpeg|gif)$/, loader: 'url-loader?limit=8192'}, //图片文件使用 url-loader 来处理,小于8kb的直接转为base64
            {test: /\.(png|jpg|gif|svg)$/,loader: 'url',
                query: {limit: 10000,name: '[name].[ext]?[hash]'}//设置图片名称扩展名
            },
            { test: /\.jade$/, loader: "jade-loader" },//.jade 文件使用 jade-loader 来编译处理
            { test: /\.ejs$/, loader: "ejs-loader" },//.ejs 文件使用 ejs-loader 来编译处理
            { test: /\.handlebars$/, loader: "handlebars-loader" },//.handlebars 文件使用handlebars-loader来编译处理handlebars模板文件
            { test: /\.dot$/, loader: "dot-loader" },//.dot 文件使用 dot-loader 来编译处理dot模板文件
            { test: /\.vue$/, loader: "vue-loader" },//.vue 文件使用 vue-loader 来编译处理
            { test: /\.coffee$/, loader: 'coffee-loader' },//.coffee 文件使用 coffee-loader 来编译处理
            { test: /\.html$/,loader: 'vue-html'},
            { test: /\.woff$/,loader: "url?limit=10000&minetype=application/font-woff"},
            { test: /\.ttf$/,loader: "file"},
            { test: /\.eot$/,loader: "file"},
            { test: /\.svg$/,loader: "file"}
            ]
        },
        //分内置插件和外置插件
        plugins: [
            //使用了一个 CommonsChunkPlugin 的插件,它用于提取多个入口文件的公共脚本部分,然后生成一个common.js来方便多页面之间进行复用。
            new webpack.optimize.CommonsChunkPlugin('common.js'),
            new webpack.optimize.UglifyJsPlugin({//压缩文件
              compressor: {
                warnings: false,//supresses warnings, usually from module minification
              },
              except: ['$super', '$', 'exports', 'require']    //排除关键字(可选)
            }),
            new webpack.DefinePlugin({// definePlugin 接收字符串插入到代码当中, 所以你需要的话可以写上 JS 的字符串
                 __DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'true')),
                 __PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false'))
            }),
            new webpack.ProvidePlugin({//把一个全局变量插入到所有的代码中,支持jQuery plugin的使用;使用ProvidePlugin加载使用频率高的模块
                 //provide $, jQuery and window.jQuery to every script
                 $: "jquery",
                 jQuery: "jquery",
                 "window.jQuery": "jquery"
             }),
            new webpack.NoErrorsPlugin(), //允许错误不打断程序
            new TransferWebpackPlugin([ //把指定文件夹下的文件复制到指定的目录
              {from: 'www'}
            ], path.resolve(__dirname,"src")),
            new HtmlwebpackPlugin({//用于生产符合要求的html文件;
               title: 'Hello World app',
               filename: 'assets/admin.html'
            })
        ],
        //其它解决方案配置
        resolve: {
            root: 'E:/github/flux-example/src', //绝对路径, 查找module的话从这里开始查找(可选)
            extensions: ['', '.js', '.html', '.css', '.scss'], //自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
            alias: {                            //模块别名定义,方便后续直接引用别名,无须多写长长的地址//后续直接 require('AppStore') 即可
                AppStore : 'js/stores/AppStores.js',
                ActionType : 'js/actions/ActionType.js',
                AppAction : 'js/actions/AppAction.js'
            },
            modulesDirectories: [//取相对路径,所以比起 root ,所以会多很多路径。查找module(可选)
                 'node_modules',
                 'bower_components',
                 'lib',
                 'src'
            ]
        }
         
    };
     
    if (process.env.NODE_ENV === 'production') {
      module.exports.devtool = '#source-map'
      // http://vue-loader.vuejs.org/en/workflow/production.html
      module.exports.plugins = (module.exports.plugins || []).concat([
        new webpack.DefinePlugin({
          'process.env': {
            NODE_ENV: '"production"'
          }
        }),
        new webpack.optimize.UglifyJsPlugin({
          compress: {
            warnings: false
          }
        }),
        //为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
        new webpack.optimize.OccurenceOrderPlugin()
      ])
    }

    plugins中包含很多的内置插件和外部插件,它们都有各自的功能,用来处理相关的文件,这里只是罗列了部分,具体用法请看webpack入门和实战(二):全面理解和运用plugins和loader

    就像我在前面提到的,webpack.config.js的写法和在Node里的写法相同,我们主要看的就是文件中的module.exports里面的内容

    • entry 是指入口文件的配置项,它是一个数组的原因是webpack允许多个入口点。
    • output是指输出文件的配置项
      • path - 表示输出文件的路径
      • filename - 表示输出文件的文件名
    • plugins 顾名思义,使用插件可以给webpack添加更多的功能,使webpack更加的灵活和强大,webpack有两种类型的插件:
      • webpack内置的插件

    // 首先要先安装webpack模块

    var webpack = require("webpack");
    module.exports = {
        new webpack.optimize.UglifyJsPlugin({
          compressor: {
            warnings: false,
          },
        })
    };
      •  webpack外置插件
    //npm install component-webpack-plugin 先要在安装该模版
    var ComponentPlugin = require("component-webpack-plugin");
    module.exports = {
        plugins: [
            new ComponentPlugin()
        ]
    }

    更多的插件以及插件的用法,大家可以到webpack的插件上查看。

    • module 配置处理文件的选项
      • loaders 一个含有wepback中能处理不同文件的加载器的数组
        • test 用来匹配相对应文件的正则表达式
        • loaders 告诉webpack要利用哪种加载器来处理test所匹配的文件
      • loaders 的安装方法

            $ npm install xxx-loader --save-dev

    • resolve:其它解决方案配置;
      • resolve.root,绝对路径, 查找module的话从这里开始查找(可选)
      • resolve.modulesDirectories,取相对路径,所以比起 root ,所以会多 parse 很多路径。查找module(可选)
      • resolve.extensions,自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
      • resolve.alias,模块别名定义,方便后续直接引用别名,无须多写长长的地址

    三、利用webpack实现在页面上合理使用打包过后的js文件和图片

    示例如下:

    webpack_test目录结构如下:

    最终完成版的目录结构为:

     index.html代码如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>demo1</title>
    </head>
    <body>
        <div id="content"></div>
        <img src="./build/img/demo.png">
        <script src="./build/js/index.js"></script>
    </body>
    </html>

    index.js代码如下:

    require('./index.css');

    index.css代码如下:

    #content{
        width:121px;
        height:140px;
        background-color: red;
    }

    demo.png自己随便找一张即可;

    根据webpack.config.js的配置情况,操作步骤如下:

    • 全局安装webpack,npm install webpack -g
    • 进入到webpack_test目录下,初始化生成package.json文件,npm init
    • 需要安装的loader有css-loader、style-loader、url-loader,webpack, npm install css-loader style-loader url-loader webpack--save-dev
    • 执行webpack,生成打包过后的build/js/index.js,build/img/demo.png
    • 在index.html中引入即可

    效果如下:

     源码地址为:http://download.csdn.net/detail/wdlhao/9612173,有需要的同学可以自行下载练习;

     四、理解webpack支持commonJS和AMD/CMD两种模块机制进行打包

     1.AMD/CMD模式:

         AMD 规范在这里:https://github.com/amdjs/amdjs-api/wiki/AMD,CMD 规范在这里:https://github.com/seajs/seajs/issues/242

    • AMD(Asynchronous Module Definition) 是 RequireJS 在推广过程中对模块定义的规范化产出。(即RequireJS模块规范)

    RequireJS是一个工具库,主要用于客户端的模块管理。它可以让客户端的代码分成一个个模块,实现异步或动态加载,从而提高代码的性能和可维护性。它的模块管理遵守AMD规范(Asynchronous Module Definition)。RequireJS的基本思想是,通过define方法,将代码定义为模块;通过require方法,实现代码的模块加载。首先,将require.js嵌入网页,然后就能在网页中进行模块化编程了。<script data-main="scripts/main" src="scripts/require.js"></script>上面代码的data-main属性不可省略,用于指定主代码所在的脚本文件,在上例中为scripts子目录下的main.js文件。用户自定义的代码就放在这个main.js文件中。

    • CMD(Common Module Definition )是 SeaJS 在推广过程中对模块定义的规范化产出。(即SeaJS模块规范)

    SeaJS是一个遵循CMD规范的JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制。

    • CommonJS Modules/2.0 规范,是 BravoJS 在推广过程中对模块定义的规范化产出。

    CommonJS API定义很多普通应用程序(主要指非浏览器的应用)使用的API,从而填补了这个空白。它的终极目标是提供一个类似Python,Ruby和Java标准库。这样的话,开发者可以使用CommonJS API编写应用程序,然后这些应用可以运行在不同的JavaScript解释器和不同的主机环境中。在兼容CommonJS的系统中,你可以实用JavaScript程序开发:

    • 服务器端JavaScript应用程序
    • 命令行工具
    • 图形界面应用程序
    • 混合应用程序(如,Titanium或Adobe AIR)

    还有不少⋯⋯这些规范的目的都是为了 JavaScript 的模块化开发,特别是在浏览器端的。目前这些规范的实现都能达成浏览器端模块化开发的目的。

     2、AMD/CMD模式区别

    2.1从官方推荐的写法上面得出:

    CMD ----- 依赖就近

    Js代码 
    //CMD 
    define(function(require,exports,module){ 
       var a = require('./a'); 
       a.doSomthing(); 
    });

    AMD ----- 依赖前置

    Js代码 
    //AMD 
    define(['./a','./b'],function(a,b){ 
    //...... 
    a.doSomthing(); 
    //...... 
    b.doSomthing(); 
    })

    当然AMD也支持CMD的写法。

    2.2、执行顺序上:

      • CMD是延迟执行,推崇的是as lazy as possible
      • AMD是提前执行,requireJS从2.0开始可以延迟执行

    2.3、api设计角度:

      • CMD的API推崇职责单一,没有全局的require
      • AMD的API默认是一个当多个用:比如require有全局的和局部的。
    展开全文
  • 在做vue项目和react项目时,都用到了webpackwebpack帮助我们很好地提高了工作效率,但是一直以来没有对其原理进行探究,略有遗憾。 因为使用一个工具,能够深入了解其原理才能更好地使用。 这篇文章将大致分为三个...

      在做vue项目和react项目时,都用到了webpack。webpack帮助我们很好地提高了工作效率,但是一直以来没有对其原理进行探究,略有遗憾。 因为使用一个工具,能够深入了解其原理才能更好地使用。 这篇文章将大致分为三个部分进行解读:

    • webpack打包简单介绍
    • 输入webpack后发生了什么,整个运行机制大致是怎样的? 
    • 如何理解打包出的bundle.js?
    • 如何实现一个简单的webpack打包工具? 
    • 打包优化

     

    第一部分: webpack打包简单介绍

         当一个项目使用webpack打包时,webpack会认为所有的文件都是模块并将其打包到一个文件中但是webpack只能识别js文件,所以对于其他文件,我们需要使用loader来完成打包。 

      通过webpack打包,我们能很好地解决前端项目中的依赖问题,这样可以帮助我们专注于实现项目的代码逻辑,而非是依赖、命名冲突等。

     

    第二部分: 输入webpack后发生了什么, 整个运行机制大致是怎样的?   

        一般情况下,我们都会在根目录下配置一个 webpack.config.js 文件,用于配置webpack打包。 当我们打开控制台时,输入webpack, 就会根据配置文件对项目进行打包了。但是,在这个过程中究竟发生了什么呢? 

      

    执行脚本 bin/webpack.js

      当在cmd中输入一个命令执行时,实际上执行的都是一个类似于可执行的二进制文件,比如执行node命令、ping命令时都是这样的, 在项目的node_modules下的webpack根目录下找到package.json, 可以看到下面的一个kv:

      "bin": {
        "webpack": "./bin/webpack.js"
      },

      这就说明在执行二进制文件时,会运行 ./bin/webpack.js文件,找到这个文件,我们可以看到主要的代码如下:

     这就说明在执行二进制文件时,会运行 ./bin/webpack.js文件,找到这个文件,我们可以看到主要的代码如下:

    // 引入nodejs的path模块
    var path = require("path");
    
    // 获取 /bin/webpack.js的绝对路径
    try {
        var localWebpack = require.resolve(path.join(process.cwd(), "node_modules", "webpack", "bin", "webpack.js"));
        if(__filename !== localWebpack) {
            return require(localWebpack);
        }
    } catch(e) {}
    
    //  引入yargs模块,用于处理命令行参数
    var yargs = require("yargs")
        .usage("webpack " + require("../package.json").version + "\n" +
            "Usage: https://webpack.js.org/api/cli/\n" +
            "Usage without config file: webpack <entry> [<entry>] <output>\n" +
            "Usage with config file: webpack");
    
    // 使用yargs来初始化命令行对象
    require("./config-yargs")(yargs);
    
    var DISPLAY_GROUP = "Stats options:";
    var BASIC_GROUP = "Basic options:";
    
    
    // 命令行参数的基本配置
    yargs.options({
        "json": {
            type: "boolean",
            alias: "j",
            describe: "Prints the result as JSON."
        },
        "progress": {
            type: "boolean",
            describe: "Print compilation progress in percentage",
            group: BASIC_GROUP
        },
        // 省略若干
    });
    
    // yargs模块提供的argv对象,用来读取命令行参数,alias可以设置某个命令的简称,方便输入。 
    var argv = yargs.argv;
    
    if(argv.verbose) {
        argv["display"] = "verbose";
    }
    
    // argv为读取命令行的参数,通过conver-argv配置文件将命令行中的参数经过处理保存在options对象中
    var options = require("./convert-argv")(yargs, argv);
    
    
    function ifArg(name, fn, init) {
        if(Array.isArray(argv[name])) {
            if(init) init();
            argv[name].forEach(fn);
        } else if(typeof argv[name] !== "undefined") {
            if(init) init();
            fn(argv[name], -1);
        }
    }
    
    // /bin/webpack.js的核心函数
    function processOptions(options) {
    
        // 支持promise风格的异步回调(promise是承诺的意思)
        if(typeof options.then === "function") {
            options.then(processOptions).catch(function(err) {
                console.error(err.stack || err);
                process.exit(1); // eslint-disable-line
            });
            return;
        }
    
        // 得到webpack编译对象时数组情况下的options
        var firstOptions = [].concat(options)[0];
        var statsPresetToOptions = require("../lib/Stats.js").presetToOptions;
    
        // 设置输出option
        var outputOptions = options.stats;
        if(typeof outputOptions === "boolean" || typeof outputOptions === "string") {
            outputOptions = statsPresetToOptions(outputOptions);
        } else if(!outputOptions) {
            outputOptions = {};
        }
    
    
        // 省略若干。。。。。
    
    
        // 引入主入口模块 /lib/webpack.js
        var webpack = require("../lib/webpack.js");
    
        
        var compiler;
        try {
    
            // 使用webpack函数开始对获得的配置对象进行编译, 返回compiler
            compiler = webpack(options);
        } catch(e) {
            // 省略若干。。。
        }
    
    
    
        function compilerCallback(err, stats) {
            // 编译完成之后的回调函数
        }
    
    
        // 如果有watch配置,则及时进行编译。
        if(firstOptions.watch || options.watch) {
            var watchOptions = firstOptions.watchOptions || firstOptions.watch || options.watch || {};
            if(watchOptions.stdin) {
                process.stdin.on("end", function() {
                    process.exit(0); // eslint-disable-line
                });
                process.stdin.resume();
            }
            compiler.watch(watchOptions, compilerCallback);
            console.log("\nWebpack is watching the files…\n");
        } else
            compiler.run(compilerCallback);
    
    }
    
    
    // 处理这些配置选项,即调用上面的函数
    processOptions(options);
    

       实际上上面的这段代码还是比较好理解的,就是使用相关模块获取到配置对象,然后从./lib/webpack.js 中获取到webpack来进行编译, 然后根据配置选项进行相应的处理。 这里比较重要的就是webpack.js函数,我们来看看源码。 

     ./lib/webpack.js解析

    // 建立webpack主函数,下面某些代码被省略了。
    function webpack(options, callback) {
        
        let compiler;
        if(Array.isArray(options)) {
            // 如果webapck是一个数组,则一次执行
            compiler = new MultiCompiler(options.map(options => webpack(options)));
        } else if(typeof options === "object") {
    
            // 一般情况下webpack配置应该是一个对象,使用默认的处理配置中的所有选项
            new WebpackOptionsDefaulter().process(options);

             // 实例化一个 Compiler,Compiler 会继承一个 Tapable 插件框架
             // Compiler 实例化后会继承到 apply、plugin 等调用和绑定插件的方法

            compiler = new Compiler();
            
            compiler.context = options.context;
            compiler.options = options;
            new NodeEnvironmentPlugin().apply(compiler);
            if(options.plugins && Array.isArray(options.plugins)) {
                // 对于选项中的插件,进行使用、编译
                compiler.apply.apply(compiler, options.plugins);
            }
            compiler.applyPlugins("environment");
            compiler.applyPlugins("after-environment");
            compiler.options = new WebpackOptionsApply().process(options, compiler);
        } else {
            throw new Error("Invalid argument: options");
        }
    
        return compiler;
    }
    exports = module.exports = webpack;
    

    注意:

      一是 Compiler,实例化它会继承 Tapable ,这个 Tapable 是一个插件框架,通过继承它的一系列方法来实现注册和调用插件,我们可以看到在 webpack 的源码中,存在大量的 compiler.apply、compiler.applyPlugins、compiler.plugin 等Tapable方法的调用。Webpack 的 plugin 注册和调用方式,都是源自 Tapable 。Webpack 通过 plugin 的 apply 方法安装该 plugin,同时传入一个 webpack 编译对象(Webpack compiler object)。
      二是 WebpackOptionsApply 的实例方法 process (options, compiler),这个方法将会针对我们传进去的webpack 编译对象进行逐一编译,接下来我们再来仔细看看这个模块。

     

    调用 lib/WebpackOptionsApply.js 模块的 process 方法来逐一编译 webpack 编译对象的各项(这里的文件才是比较核心的)

    /*
        MIT License http://www.opensource.org/licenses/mit-license.php
        Author Tobias Koppers @sokra
    */
    "use strict";
    
    // 这里引入了若干插件(数十个)
    
    
    // 给webpack中的配置对象使用插件
    class WebpackOptionsApply extends OptionsApply {
        constructor() {
            super();
        }
    
        // 处理配置独享主要函数
        process(options, compiler) {
            let ExternalsPlugin;
            // 根据options来配置options
            compiler.outputPath = options.output.path;
            compiler.recordsInputPath = options.recordsInputPath || options.recordsPath;
            compiler.recordsOutputPath = options.recordsOutputPath || options.recordsPath;
            compiler.name = options.name;
            compiler.dependencies = options.dependencies;
            if(typeof options.target === "string") {
                let JsonpTemplatePlugin;
                let NodeSourcePlugin;
                let NodeTargetPlugin;
                let NodeTemplatePlugin;
    
                switch(options.target) {
                    case "web":
                        // 省略处理代码
                    case "webworker":
                        // 省略处理代码
                    case "node":
                    case "async-node":
                        // 省略处理代码
                        break;
                    case "node-webkit":
                        // 省略处理代码
                        break;
                    case "atom":
                    case "electron":
                    case "electron-main":
                        // 省略处理代码
                    case "electron-renderer":
                        // 省略处理代码
                    default:
                        throw new Error("Unsupported target '" + options.target + "'.");
                }
            } else if(options.target !== false) {
                options.target(compiler);
            } else {
                throw new Error("Unsupported target '" + options.target + "'.");
            }
    
            // 根据配置来决定是否生成sourcemap
            if(options.devtool && (options.devtool.indexOf("sourcemap") >= 0 || options.devtool.indexOf("source-map") >= 0)) {
                // 省略若干
                // sourcemap代码下通常都会指明源地址
                comment = legacy && modern ? "\n/*\n//@ source" + "MappingURL=[url]\n//# source" + "MappingURL=[url]\n*/" :
                    legacy ? "\n/*\n//@ source" + "MappingURL=[url]\n*/" :
                    modern ? "\n//# source" + "MappingURL=[url]" :
                    null;
                let Plugin = evalWrapped ? EvalSourceMapDevToolPlugin : SourceMapDevToolPlugin;
                compiler.apply(new Plugin({
                    filename: inline ? null : options.output.sourceMapFilename,
                    moduleFilenameTemplate: options.output.devtoolModuleFilenameTemplate,
                    fallbackModuleFilenameTemplate: options.output.devtoolFallbackModuleFilenameTemplate,
                    append: hidden ? false : comment,
                    module: moduleMaps ? true : cheap ? false : true,
                    columns: cheap ? false : true,
                    lineToLine: options.output.devtoolLineToLine,
                    noSources: noSources,
                }));
            } else if(options.devtool && options.devtool.indexOf("eval") >= 0) {
                legacy = options.devtool.indexOf("@") >= 0;
                modern = options.devtool.indexOf("#") >= 0;
                comment = legacy && modern ? "\n//@ sourceURL=[url]\n//# sourceURL=[url]" :
                    legacy ? "\n//@ sourceURL=[url]" :
                    modern ? "\n//# sourceURL=[url]" :
                    null;
                compiler.apply(new EvalDevToolModulePlugin(comment, options.output.devtoolModuleFilenameTemplate));
            }
    
    
            compiler.apply(
                new CompatibilityPlugin(),
                // 使用相关插件进行处理
            );
            
            return options;
        }
    }
    
    module.exports = WebpackOptionsApply;
    

    不出意外,这个构造函数被实例化后会返回一个对象。 然后由compiler处理

    到这基本上就是大致流程了,我们可以再介绍上一步中的常用的插件:UglifyJsPlugin.js 

    lib/optimize/UglifyJsPlugin.js
    
    // 引入一些依赖,主要是与压缩代码、sourceMap 相关
    var SourceMapConsumer = require("webpack-core/lib/source-map").SourceMapConsumer;
    var SourceMapSource = require("webpack-core/lib/SourceMapSource");
    var RawSource = require("webpack-core/lib/RawSource");
    var RequestShortener = require("../RequestShortener");
    var ModuleFilenameHelpers = require("../ModuleFilenameHelpers");
    var uglify = require("uglify-js");
    
    // 定义构造器函数
    function UglifyJsPlugin(options) {
        ...
    }
    // 将构造器暴露出去
    module.exports = UglifyJsPlugin;
    
    // 按照 Tapable 风格编写插件
    UglifyJsPlugin.prototype.apply = function(compiler) {
        ...
        // 编译器开始编译
        compiler.plugin("compilation", function(compilation) {
            ...
            // 编译器开始调用 "optimize-chunk-assets" 插件编译
            compilation.plugin("optimize-chunk-assets", function(chunks, callback) {
                var files = [];
                ...
                files.forEach(function(file) {
                    ...
                    try {
                        var asset = compilation.assets[file];
                        if(asset.__UglifyJsPlugin) {
                            compilation.assets[file] = asset.__UglifyJsPlugin;
                            return;
                        }
                        if(options.sourceMap !== false) {
                        // 需要 sourceMap 时要做的一些操作...
                        } else {
                            // 获取读取到的源文件
                            var input = asset.source(); 
                            ...
                        }
                        // base54 编码重置
                        uglify.base54.reset(); 
                        // 将源文件生成语法树
                        var ast = uglify.parse(input, {
                            filename: file
                        });
                        // 语法树转换为压缩后的代码
                        if(options.compress !== false) {
                            ast.figure_out_scope();
                            var compress = uglify.Compressor(options.compress); // eslint-disable-line new-cap
                            ast = ast.transform(compress);
                        }
                        // 处理混淆变量名
                        if(options.mangle !== false) {
                            ast.figure_out_scope();
                            ast.compute_char_frequency(options.mangle || {});
                            ast.mangle_names(options.mangle || {});
                            if(options.mangle && options.mangle.props) {
                                uglify.mangle_properties(ast, options.mangle.props);
                            }
                        }
                        // 定义输出变量名
                        var output = {};
                        // 处理输出的注释
                        output.comments = Object.prototype.hasOwnProperty.call(options, "comments") ? options.comments : /^\**!|@preserve|@license/;
                        // 处理输出的美化
                        output.beautify = options.beautify;
                        for(var k in options.output) {
                            output[k] = options.output[k];
                        }
                        // 处理输出的 sourceMap
                        if(options.sourceMap !== false) {
                            var map = uglify.SourceMap({ // eslint-disable-line new-cap
                                file: file,
                                root: ""
                            });
                            output.source_map = map; // eslint-disable-line camelcase
                        }
                        // 将压缩后的数据输出
                        var stream = uglify.OutputStream(output); // eslint-disable-line new-cap
                        ast.print(stream);
                        if(map) map = map + "";
                        stream = stream + "";
                        asset.__UglifyJsPlugin = compilation.assets[file] = (map ?
                            new SourceMapSource(stream, file, JSON.parse(map), input, inputSourceMap) :
                            new RawSource(stream));
                        if(warnings.length > 0) {
                            compilation.warnings.push(new Error(file + " from UglifyJs\n" + warnings.join("\n")));
                        }
                    } catch(err) {
                        // 处理异常
                        ...
                    } finally {
                        ...
                    }
                });
                // 回调函数
                callback();
            });
            compilation.plugin("normal-module-loader", function(context) {
                context.minimize = true;
            });
        });
    };

    现在我们回过头来再看看整体流程,当我们在命令行输入 webpack 命令,按下回车时都发生了什么:

    1. 执行 bin 目录下的 webpack.js 脚本,解析命令行参数以及开始执行编译。
    2. 调用 lib 目录下的 webpack.js 文件的核心函数 webpack ,实例化一个 Compiler,继承 Tapable 插件框架,实现注册和调用一系列插件。
    3. 调用 lib 目录下的 /WebpackOptionsApply.js 模块的 process 方法,使用各种各样的插件来逐一编译 webpack 编译对象的各项。
    4. 在3中调用的各种插件编译并输出新文件。

     

    第三部分:如何理解打包出的bundle.js?

    一个入口文件

    // webpack.config.js 
    module.exports = {
        entry: ["./index.js"],  //输入
        output: {               //输出
            path: __dirname + "/dist",
            filename: "bundle.js"
        },
        watch: true,
        module: {
            loaders: [
            {
                test: /\.jsx?$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
                query: {
                    presets: ['es2015', 'react']
                }
            },
            {
                test: /\.css$/,
                loader: 'style-loader!css-loader'
            },
            {
                test: /\.less$/,
                use: [{
                     loader: "style-loader" // creates style nodes from JS strings
                 }, {
                     loader: "css-loader" // translates CSS into CommonJS
                 }, {
                     loader: "less-loader" // compiles Less to CSS
                 }]
            },
            {
                test: /\.(jpg|png|svg)$/,
                loader: 'url-loader'
            }
            ]
        }
    }
    
    
    // index.js 
    

      import React from "react";
      import ReactDom from 'react-dom'

      import App from './pages/app.jsx'
      ReactDom.render(
        <App/>,
             document.querySelector('#app')
      )

    // bundle.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,  //模块ID
    /******/             l: false,  //是否load
    /******/             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;  //m是modules
    /******/
    /******/     // expose the module cache  //暴露模块缓存
    /******/     __webpack_require__.c = installedModules;  //c是cache
    /******/
    /******/     // define getter function for harmony exports  //定义协调输出的getter函数
    /******/     __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 = 86);
    /******/ })
    /************************************************************************/
    /******/ ([
    /* 0 */  //第0个函数
    /***/ function(module, exports) {
    
        console.log('index');
    
    /***/ },
    

    /* 1 */  //第1个函数
    /***/ (function(module, exports, __webpack_require__) {

          "use strict";

        function reactProdInvariant(code) {
        var argCount = arguments.length - 1;

        var message = 'Minified React error #' + code + '; visit ' + 'http://facebook.github.io/react/docs/error-decoder.html?invariant=' + code;

        for (var argIdx = 0; argIdx < argCount; argIdx++) {
          message += '&args[]=' + encodeURIComponent(arguments[argIdx + 1]);
        }

        message += ' for the full message or use the non-minified dev environment' + ' for full errors and additional helpful warnings.';

        var error = new Error(message);
        error.name = 'Invariant Violation';
        error.framesToPop = 1; // we don't care about reactProdInvariant's own frame

        throw error;
      }

      module.exports = reactProdInvariant;

    /***/ }),

      // 省略若干。。。。

    /******/ ]);
    1. 可以看到,这个bundle.js是一个自执行函数,前65行都在定义这个自执行函数,最后传入了一个数组作为参数,因为只有一个js文件,这里的数组长度为1,并且数组里的每一个元素都是一个自执行函数,自执行函数中包含着index.js里的内容。 
    2. 即整个bundle.js文件是一个传入了 包含若干个模块的数组 作为参数即传入的modules是一个数组。 
    3. 在这个bundle.js文件中的自执行函数中定义了一个webpack打包的函数 __webpack_require__, 这个函数式一个打包的核心函数, 接收一个moduleId作为参数,moduleId是一个数字,实际上就是整个自执行函数接收的数组参数的index值。 即整个传入的module数组,每一个元素都是一个module,我们为之定义一个特定的moduleId,进入函数,首先判断要加载的模块是否已经存在,如果已经存在, 就直接返回installedModules[moduleId].exports,这样就保证了所有的模块只会被加载一次,而不会被多次加载。 如果说这个模块还没有被加载,那么我们就创建一个installedModules[moduleId], 他是一个对象,包括i属性(即moduleId),l属性(表示这个模块是否已经被加载, 初始化为false), exports 属性它的内容是每个模块想要导出的内容, 接下来执行  modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 函数进行调用,那么这个函数具体是如何执行的呢? 首先保证在module.exports上进行调用这个函数,然后传入了module参数,即我们想要调用的这个模块,传入module.exports ,那么在每一个模块中使用的module和module.exports就都是属于这个模块的了, 同时再传入 __webpack_require__这样我们就可以在每一个模块中继续使用了加载器了,最后,导出这个模块。 调用完成之后,将l设置为true,表示已经加载,最后导出module.exports,即导出加载到的模块。
    4. 在自执行函数的末尾我们可以看到这个自执行函数最终返回了一个 __webpack_require__ 调用,也就是说返回了一个模块,因为__webpck_require__函数本身就会返回一个模块。 并且这个 __webpack_require__调用接收的参数是一个 moduleId ,且指明了其值为86。 也就是说入口文件的 moduleId 为86, 我们来看一看模块 86 的内容是什么。即在这个bundle.js函数执行之后,实际上得到的第一部分内容是 86 模块的内容。 
    /* 86 */
    /***/ (function(module, exports, __webpack_require__) {
    
    module.exports = __webpack_require__(87);
    
    
    /***/ }),

       模块86非常简单,就是首先通过 __webpack_require__(87) 引入了 moduleId 为87的模块, 然后我们看看87模块是什么。

    /* 87 */
    /***/ (function(module, exports, __webpack_require__) {
    
    "use strict";
    
    var _react = __webpack_require__(9);
    var _react2 = _interopRequireDefault(_react);
    var _reactDom = __webpack_require__(103);
    var _reactDom2 = _interopRequireDefault(_reactDom);
    var _app = __webpack_require__(189);
    var _app2 = _interopRequireDefault(_app);
    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
    _reactDom2.default.render(_react2.default.createElement(_app2.default, null), document.querySelector('#app'));
    /***/ }),

      在这一部分的开头,我们也看到了index.js的内容,主要任务就是引入了 react 、react-dom、引入了App组件、最后进行渲染。 同样地,这里我们可以看到,在这个模块中,通过 __webpack_reuqire__(9) 引入了_react(这里的react添加了下划线,表示这里的react是没有对外暴露的), 然后使用_interopRequireDefault这个函数处理 --- 首先判断引入的是否是一个对象并且同时满足这个对象是否满足es6中的module导出,如果满足,就直接返回这个对象,如果不满足, 就返回一个值为obj的对象来进一步处理。 最后一步就是使用引入的各个方法来讲 App 模块挂载到 id为app为的元素下。 到这里,可以看出引入了多个模块,我们下面分别分析 __webpack_require__(9) 的react模块以及__webpack_require__(189) 的 app 模块,即一个是从外部定义的模块,一个是我们自己写的模块。这两个类型不同的模块有了区分之后,我们就可以大致理清楚整个 bundle.js 的脉络了。 

    __webpack_require__(9)

    /* 9 */
    /***/ (function(module, exports, __webpack_require__) {
    "use strict";
    module.exports = __webpack_require__(19);
    /***/ }),

    进入了__webpack_require__(9)模块我们看到,我们需要去寻找 19 模块。 下面我们看看19模块。 

    /* 19 */
    /***/ (function(module, exports, __webpack_require__) {
    
    "use strict";
    // 这里说明了react是从外部注入的。 
    /* WEBPACK VAR INJECTION */(function(process) {/**
    
    // 下面的这几行和我们直接打开react.js代码的前几行是一样的,说明这些代码确实是直接引入的。
     * Copyright 2013-present, Facebook, Inc.
     * All rights reserved.
     *
     * This source code is licensed under the BSD-style license found in the
     * LICENSE file in the root directory of this source tree. An additional grant
     * of patent rights can be found in the PATENTS file in the same directory.
     *
     */
    
    
    var _assign = __webpack_require__(4);
    
    var ReactBaseClasses = __webpack_require__(53);
    var ReactChildren = __webpack_require__(88);
    var ReactDOMFactories = __webpack_require__(92);
    var ReactElement = __webpack_require__(15);
    var ReactPropTypes = __webpack_require__(96);
    var ReactVersion = __webpack_require__(99);
    
    var createReactClass = __webpack_require__(100);
    var onlyChild = __webpack_require__(102);
    
    var createElement = ReactElement.createElement;
    var createFactory = ReactElement.createFactory;
    var cloneElement = ReactElement.cloneElement;
    
    if (process.env.NODE_ENV !== 'production') {
      var lowPriorityWarning = __webpack_require__(36);
      var canDefineProperty = __webpack_require__(27);
      var ReactElementValidator = __webpack_require__(57);
      var didWarnPropTypesDeprecated = false;
      createElement = ReactElementValidator.createElement;
      createFactory = ReactElementValidator.createFactory;
      cloneElement = ReactElementValidator.cloneElement;
    }
    
    var __spread = _assign;
    var createMixin = function (mixin) {
      return mixin;
    };
    
    if (process.env.NODE_ENV !== 'production') {
      var warnedForSpread = false;
      var warnedForCreateMixin = false;
      __spread = function () {
        lowPriorityWarning(warnedForSpread, 'React.__spread is deprecated and should not be used. Use ' + 'Object.assign directly or another helper function with similar ' + 'semantics. You may be seeing this warning due to your compiler. ' + 'See https://fb.me/react-spread-deprecation for more details.');
        warnedForSpread = true;
        return _assign.apply(null, arguments);
      };
    
      createMixin = function (mixin) {
        lowPriorityWarning(warnedForCreateMixin, 'React.createMixin is deprecated and should not be used. ' + 'In React v16.0, it will be removed. ' + 'You can use this mixin directly instead. ' + 'See https://fb.me/createmixin-was-never-implemented for more info.');
        warnedForCreateMixin = true;
        return mixin;
      };
    }
    
    var React = {
      // Modern
    
      Children: {
        map: ReactChildren.map,
        forEach: ReactChildren.forEach,
        count: ReactChildren.count,
        toArray: ReactChildren.toArray,
        only: onlyChild
      },
    
      Component: ReactBaseClasses.Component,
      PureComponent: ReactBaseClasses.PureComponent,
    
      createElement: createElement,
      cloneElement: cloneElement,
      isValidElement: ReactElement.isValidElement,
    
      // Classic
    
      PropTypes: ReactPropTypes,
      createClass: createReactClass,
      createFactory: createFactory,
      createMixin: createMixin,
    
      // This looks DOM specific but these are actually isomorphic helpers
      // since they are just generating DOM strings.
      DOM: ReactDOMFactories,
    
      version: ReactVersion,
    
      // Deprecated hook for JSX spread, don't use this for anything.
      __spread: __spread
    };
    
    if (process.env.NODE_ENV !== 'production') {
      var warnedForCreateClass = false;
      if (canDefineProperty) {
        Object.defineProperty(React, 'PropTypes', {
          get: function () {
            lowPriorityWarning(didWarnPropTypesDeprecated, 'Accessing PropTypes via the main React package is deprecated,' + ' and will be removed in  React v16.0.' + ' Use the latest available v15.* prop-types package from npm instead.' + ' For info on usage, compatibility, migration and more, see ' + 'https://fb.me/prop-types-docs');
            didWarnPropTypesDeprecated = true;
            return ReactPropTypes;
          }
        });
    
        Object.defineProperty(React, 'createClass', {
          get: function () {
            lowPriorityWarning(warnedForCreateClass, 'Accessing createClass via the main React package is deprecated,' + ' and will be removed in React v16.0.' + " Use a plain JavaScript class instead. If you're not yet " + 'ready to migrate, create-react-class v15.* is available ' + 'on npm as a temporary, drop-in replacement. ' + 'For more info see https://fb.me/react-create-class');
            warnedForCreateClass = true;
            return createReactClass;
          }
        });
      }
    
      // React.DOM factories are deprecated. Wrap these methods so that
      // invocations of the React.DOM namespace and alert users to switch
      // to the `react-dom-factories` package.
      React.DOM = {};
      var warnedForFactories = false;
      Object.keys(ReactDOMFactories).forEach(function (factory) {
        React.DOM[factory] = function () {
          if (!warnedForFactories) {
            lowPriorityWarning(false, 'Accessing factories like React.DOM.%s has been deprecated ' + 'and will be removed in v16.0+. Use the ' + 'react-dom-factories package instead. ' + ' Version 1.0 provides a drop-in replacement.' + ' For more info, see https://fb.me/react-dom-factories', factory);
            warnedForFactories = true;
          }
          return ReactDOMFactories[factory].apply(ReactDOMFactories, arguments);
        };
      });
    }
    
    module.exports = React;
    /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0)))
    
    /***/ }),

    这就是react.js的核心代码,但是为什么一共就100行左右的代码呢? 这里应该引入了整个 react 文件啊。 我们从内部代码可以看到,在react模块中同样又使用了 __webpack_require__  来引入了更多的文件, 这时因为react.js本身就是这么引入的文件的, https://unpkg.com/react@15.6.1/dist/react.js, 从源码上可以看到, 它采用的也是分块的模式,所以在webpack打包的时候,自然也是使用一个一个模块的形式进行打包引入了。 这样做的好处是什么呢?  因为这样可以增加代码的重用,就19模块的 var ReactBaseClasses = __webpack_require__(53); 而言, 即react的 ReactBaseClasses 模块需要使用,另外,在19模块的createReactClass也是需要的,它先引入了100模块,然后又引入了 19 模块。  并且对于大型的框架、库而言,都是需要按照模块进行编写的,不可能直接写在一个模块中。 react的19模块就介绍到这里。 

    下面我们再看看189的App模块。(这个模块是jsx文件,所以需要通过babel-loader进行转译)

    /* 189 */
    /***/ (function(module, exports, __webpack_require__) {
    
    "use strict";
    
    
    Object.defineProperty(exports, "__esModule", {
        value: true
    });
    
    var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
    
    var _react = __webpack_require__(9);
    
    var _react2 = _interopRequireDefault(_react);
    
    var _title = __webpack_require__(35);
    
    var _title2 = _interopRequireDefault(_title);
    
    var _item = __webpack_require__(85);
    
    var _item2 = _interopRequireDefault(_item);
    
    var _experience = __webpack_require__(193);
    
    var _experience2 = _interopRequireDefault(_experience);
    
    var _skill = __webpack_require__(199);
    
    var _skill2 = _interopRequireDefault(_skill);
    
    var _personal = __webpack_require__(202);
    
    var _personal2 = _interopRequireDefault(_personal);
    
    var _intro = __webpack_require__(203);
    
    var _intro2 = _interopRequireDefault(_intro);
    
    var _others = __webpack_require__(207);
    
    var _others2 = _interopRequireDefault(_others);
    
    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
    
    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
    
    function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
    
    function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
    
    __webpack_require__(214);
    
    var App = function (_React$Component) {
        _inherits(App, _React$Component);
    
        function App() {
            _classCallCheck(this, App);
    
            return _possibleConstructorReturn(this, (App.__proto__ || Object.getPrototypeOf(App)).apply(this, arguments));
        }
    
        _createClass(App, [{
            key: 'render',
            value: function render() {
                return _react2.default.createElement(
                    'div',
                    { className: 'app-wrap' },
                    _react2.default.createElement(
                        'div',
                        { className: 'sub' },
                        _react2.default.createElement(
                            'div',
                            { className: 'intro' },
                            _react2.default.createElement(_intro2.default, null)
                        ),
                        _react2.default.createElement(
                            'div',
                            { className: 'others' },
                            _react2.default.createElement(_others2.default, null)
                        )
                    ),
                    _react2.default.createElement(
                        'div',
                        { className: 'main' },
                        _react2.default.createElement(
                            'div',
                            { className: 'experience' },
                            _react2.default.createElement(_experience2.default, null)
                        ),
                        _react2.default.createElement(
                            'div',
                            { className: 'skill' },
                            _react2.default.createElement(_skill2.default, null)
                        ),
                        _react2.default.createElement(
                            'div',
                            { className: 'personal' },
                            _react2.default.createElement(_personal2.default, null)
                        )
                    )
                );
            }
        }]);
    
        return App;
    }(_react2.default.Component);
    
    exports.default = App;
    
    /***/ }),

    而下面是app.jsx 的源代码:

    import React from "react";
    
    import Title from '../components/title.jsx'
    import Item2 from '../components/item2.jsx'
    import Experience from '../components/experience.jsx'
    import Skill from '../components/skill.jsx'
    import Personal from '../components/personal.jsx'
    import Intro from '../components/intro.jsx'
    import Others from '../components/others.jsx'
    
    require('../css/app.less')
    
    class App extends React.Component{
        
        render () {
            return (
                <div className='app-wrap'>
                    <div className="sub">
                        <div className="intro">
                            <Intro/>
                        </div>
                        <div className="others">
                            <Others/>
                        </div>
                    </div>
                    <div className="main">
                        <div className="experience">
                            <Experience/>
                        </div>
                        <div className="skill">
                            <Skill/>
                        </div>
                        <div className="personal">
                            <Personal/>
                        </div>
                    </div>
                </div>
                )
        }
    }
    
    export default App; 

    在模块的开始,我们就看到这个模块的 _esModule 就被定义为了 true,那么代表这个模块是符合 es6 的module规范的,这样我们就可以直接导入导出了。 

    接下来,我们又看到了 var _react = __webpack_require__(9); 因为我们在这个文件中引入了 react 模块,但是在bundle.js最开始定义模块的时候我们知道,只要加载了一次,这个模块就会被放在 installedModules 对象中,这样,我们就可以在第二次及以后使用的过程中,直接返回 installedModules 的这个模块,而不需要重新加载了

    app模块下的app.less

    接着又引入了一些依赖和更底层的组件(不是只嵌套组件的组件),比如,在 app.jsx 中我又引入了 app.less 这个less组件, 在模块189中,我们可以看到确实有一个单独引入的less组件, __webpack_require__(214); (稍后我们看看这个模块)

    最后开始创建app组件,最后返回这个组件。 

     

    模块 214 (一个less模块)

    /* 214 */
    /***/ (function(module, exports, __webpack_require__) {
    
    // style-loader: Adds some css to the DOM by adding a <style> tag
    
    // load the styles
    var content = __webpack_require__(215);
    if(typeof content === 'string') content = [[module.i, content, '']];
    // Prepare cssTransformation
    var transform;
    
    var options = {}
    options.transform = transform
    
    // add the styles to the DOM
    var update = __webpack_require__(18)(content, options);
    
    if(content.locals) module.exports = content.locals;
    // Hot Module Replacement
    if(false) {
        // When the styles change, update the <style> tags
        if(!content.locals) {
            module.hot.accept("!!../node_modules/css-loader/index.js!../node_modules/less-loader/dist/cjs.js!./app.less", function() {
                var newContent = require("!!../node_modules/css-loader/index.js!../node_modules/less-loader/dist/cjs.js!./app.less");
                if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
                update(newContent);
            });
        }
        // When the module is disposed, remove the <style> tags
        module.hot.dispose(function() { update(); });
    }
    
    /***/ }),

    在这个模块中,我们可以看到这里首先提到使用 style-loader 将css添加到html中。 接着开始加载 style ,即 215 模块(css代码),然后判断 content 是否是一个字符串,如果是,就创建一个数组,包含这个字符串, 接下来, 使用热更新机制。 这里最重要的就是18模块,将css代码添加到html中,这个模块中的的核心函数为 addStylesToDom , 如下所示:

    function addStylesToDom (styles, options) {
        for (var i = 0; i < styles.length; i++) {
            var item = styles[i];
            var domStyle = stylesInDom[item.id];
    
            if(domStyle) {
                domStyle.refs++;
    
                for(var j = 0; j < domStyle.parts.length; j++) {
                    domStyle.parts[j](item.parts[j]);
                }
    
                for(; j < item.parts.length; j++) {
                    domStyle.parts.push(addStyle(item.parts[j], options));
                }
            } else {
                var parts = [];
    
                for(var j = 0; j < item.parts.length; j++) {
                    parts.push(addStyle(item.parts[j], options));
                }
    
                stylesInDom[item.id] = {id: item.id, refs: 1, parts: parts};
            }
        }
    }

    即接收两个参数,第一个就是将要添加的style,第二个就是一些选项, 内部对所有的style进行遍历, 然后添加进入。 

    我们可以看到215模块如下所示:

    /* 215 */
    /***/ (function(module, exports, __webpack_require__) {
    
    exports = module.exports = __webpack_require__(17)(undefined);
    // imports
    
    
    // module
    exports.push([module.i, "div.app-wrap {\n  width: 80%;\n  margin: 0 auto;\n  overflow: hidden;\n  margin-top: 10px;\n  border: thin solid #ccc;\n}\ndiv.app-wrap div.sub {\n  box-shadow: 0 0 10px gray;\n  float: left;\n  width: 35%;\n}\ndiv.app-wrap div.sub div.intro {\n  margin-bottom: 63px;\n}\ndiv.app-wrap div.main {\n  float: right;\n  width: 63%;\n  margin-right: 5px;\n}\ndiv.app-wrap div.main div.skill {\n  margin-bottom: 10px;\n}\n", ""]);
    
    // exports
    
    
    /***/ })

    即这里首先引入了 17 模块, 17模块的作用是通过css-loader注入基础代码(这个基础css代码是一个数组), 接着再push进入我写的app.less代码(注意:这里的css代码已经被less-loader转化为了css代码), 然后进行注入的,最后是导出的这个css代码。  

    app模块下的introl.jsx模块(203模块)

      这个模块的jsx代码如下:

    import React from "react"
    require('../css/intro.less')
    import protrait from '../images/portrait.png'
    
    
    class Intro extends React.Component{
        render () {
    
            return (
                <div className='intro-wrap'>
                    <div className="portrait">
                        <img src={protrait}/>
                     </div>
                    <div className="name">WayneZhu</div>
                    <div className="position">
                    <span>
                        前端开发工程师
                    </span>
                    </div>
                </div>
                )
        }
    }
    
    export default Intro; 

      选用这个模块的目的是因为这里有一个导入图片的步骤,这样,我们就可以观察图片的打包过程了。

      下面是bundle.js中的该模块:

    /* 203 */
    /***/ (function(module, exports, __webpack_require__) {
    
    "use strict";
    
    
    Object.defineProperty(exports, "__esModule", {
        value: true
    });
    
    var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
    
    var _react = __webpack_require__(9);
    
    var _react2 = _interopRequireDefault(_react);
    
    var _portrait = __webpack_require__(204);
    
    var _portrait2 = _interopRequireDefault(_portrait);
    
    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
    
    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
    
    function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
    
    function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
    
    __webpack_require__(205);
    
    var Intro = function (_React$Component) {
        _inherits(Intro, _React$Component);
    
        function Intro() {
            _classCallCheck(this, Intro);
    
            return _possibleConstructorReturn(this, (Intro.__proto__ || Object.getPrototypeOf(Intro)).apply(this, arguments));
        }
    
        _createClass(Intro, [{
            key: 'render',
            value: function render() {
    
                return _react2.default.createElement(
                    'div',
                    { className: 'intro-wrap' },
                    _react2.default.createElement(
                        'div',
                        { className: 'portrait' },
                        _react2.default.createElement('img', { src: _portrait2.default })
                    ),
                    _react2.default.createElement(
                        'div',
                        { className: 'name' },
                        'WayneZhu'
                    ),
                    _react2.default.createElement(
                        'div',
                        { className: 'position' },
                        _react2.default.createElement(
                            'span',
                            null,
                            '\u524D\u7AEF\u5F00\u53D1\u5DE5\u7A0B\u5E08'
                        )
                    )
                );
            }
        }]);
    
        return Intro;
    }(_react2.default.Component);
    
    exports.default = Intro;
    
    /***/ }),

    在这个模块中,我们可以看到webpack将图片也当做了一个模块204,然后引入了这个模块,最后直接在 图片的src中引用, 所以我们有必要看看 204 模块的内容。 

     

    204模块(png图片)


    这个模块很简单,就是将图片进行了base64编码,得到的结果如下所示:

    1

    2

    3

    4

    5

    /* 204 */

    /***/ (function(module, exports) {<br><br>// 下面的编码内容省略了大部分

    module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYUAAAFRCAYAAACbjLFxAAACC"

     

    /***/ }),

    这样,就可以直接将这个编码当做src,而不会发出请求来当做http请求了。

     当然并不是所有的图片都会被当做 模块 进行打包, 我们完全可以去请求一个本地资源, 但是对于本地资源,我们需要提前进行设置, 一般,需要在node的服务器文件中添加下面的代码:

    // node你服务器使用的静态文件
    app.use('/', express.static('./www'))

    这样,我们就可以发现,在使用图片时,可以直接是:

      <img src='/images/add.png' className='create' onClick={this.createRoom}/>

    即这里 /images/add.png 默认是在 www 这个文件夹下的,因为在node中,我们已经设置了静态文件的位置了。 这样,webpack 也只是引用,而不会将至转化为base64编码:

    _react2.default.createElement(
           "div",
      { className: "channel" },
       _react2.default.createElement(
      "span",
       null,
       "\u6240\u6709\u623F\u95F4"
       ),
       _react2.default.createElement("img", { src: "/images/add.png", className: "create", onClick: this.createRoom })
     ),
    

      这样,我们就可以发现: 这里直接使用的就是路径,引用 www 文件夹下的文件。 当然,我们也可以把www下的文件直接以模块的形式打包进来。  但是,在使用静态文件时,我们只能使用 www 下这个制定文件夹下的文件,而不能使用其他文件夹下的文件。 

      可以发现的是,在寻找文件的过程中,采用的是深度优先的遍历原则。   

      ok!  bundle.js 的内容到这里大致就比较清楚了。下面,我们尝试着实现一个简单的webpack打包工具吧。

     第四部分: 如何实现一个简单的webpack打包工具? 

    前言:

       一个webpack工具是需要很大的时间和精力来创造的,我们不可能实现所有的功能,这里只是提供一个大体的思路,完成最简单的功能,如实现使用符合commonjs规范的几个文件打包为一个文件。 

      当然,浏览器是没有办法执行commonjs规范的js文件的,所以,我们需要写成自执行函数的形式,就像webpack打包出来的bundle.js一样。

    需求: 

      我们实现的需求就是一个入口文件example.js依赖于文件a、b、c,其中a和b是和example.js在同一目录文件下的,而c是在node_modules中的, 我们要将这几个模块构建成一个js文件,输入bundle.js。 

    • bundle.js 的头部信息都是一致的,如都是一个自执行函数的定义,其中有一个核心函数 __webpack_require__ ,最终这个自执行函数返回的是入口文件的模块。 然后依次向下执行。 
    • 需要分析出各个模块之间的依赖关系,比如这里的example.js是依赖于a、b、c的。 
    • 并且我们使用require('c')的时候,会自动导入node_modules中的相关文件,那么这一定是有一个详细的查询机制的。 
    • 在生成的bundle.js文件中,每一个模块都是具有一个唯一的模块id的,引用时我们只需要引用这个id即可。 

     

    分析模块依赖关系:

      CommonJS不同于AMD,是不会在一开始声明所有依赖的。CommonJS最显著的特征就是用到的时候再require,所以我们得在整个文件的范围内查找到底有多少个require

      webpack是使用commonjs的规范来写脚本的,但是对amd、cmd的书写方式也支持的很好。 这里简单区分一下几种模块化的方法。 ADM/CMD是专门为浏览器端的模块化加载来制定的, 通常使用的方式就是define() 的方式,其中amd要求必须在文件的开头声明所有依赖的文件,而cmd则没有这个要求,而是在使用的时候require即可, 即: amd是提前加载的,而cmd是在使用时再加载的,这是两者的区别之一。Commonjs是服务器端node的书写方式,如使用的时候require,而在导出的时候使用module.export,但是如今Commonjs规范已经不仅仅只适用于服务器端了,而是也适用于桌面端,但是随着其使用越来越广泛,名字由之前的severjs改为了common.js。 而es6中的 export 和 import会在babel的编译下编译为浏览器可以执行的方式。

      怎么办呢?

       最先蹦入脑海的思路是正则。然而,用正则来匹配require,有以下两个缺点:

    1. 如果require是写在注释中,也会匹配到。
    2. 如果后期要支持require的参数是表达式的情况,如require('a'+'b'),正则很难处理。

       因此,正则行不通。

       一种正确的思路是:使用JS代码解析工具(如esprima或者acorn),将JS代码转换成抽象语法树(AST),再对AST进行遍历。这部分的核心代码是parse.js。

       在处理好了require的匹配之后,还有一个问题需要解决。那就是匹配到require之后需要干什么呢?
    举个例子:

    // example.js
    let a = require('a');
    let b = require('b');
    let c = require('c');
    

       这里有三个require,按照CommonJS的规范,在检测到第一个require的时候,根据require即执行的原则,程序应该立马去读取解析模块a。如果模块a中又require了其他模块,那么继续解析。也就是说,总体上遵循深度优先遍历算法。这部分的控制逻辑写在buildDeps.js中。

     

    寻找模块:

    在完成依赖分析的同时,我们需要解决另外一个问题,那就是如何找到模块?也就是模块的寻址问题。
    举个例子:

    // example.js
    let a = require('a');
    let b = require('b');
    let c = require('c');

    在模块example.js中,调用模块a、b、c的方式都是一样的。
    但是,实际上他们所在的绝对路径层级并不一致:a和bexample同级,而c位于与example同级的node_modules中。所以,程序需要有一个查找模块的算法,这部分的逻辑在resolve.js中。

    目前实现的查找逻辑是:

    1. 如果给出的是绝对路径/相对路径,只查找一次。找到?返回绝对路径。找不到?返回false。
    2. 如果给出的是模块的名字,先在入口js(example.js)文件所在目录下寻找同名JS文件(可省略扩展名)。找到?返回绝对路径。找不到?走第3步。
    3. 在入口js(example.js)同级的node_modules文件夹(如果存在的话)查找。找到?返回绝对路径。找不到?返回false。

    当然,此处实现的算法还比较简陋,之后有时间可以再考虑实现逐层往上的查找,就像nodejs默认的模块查找算法那样。

     

    拼接 bundle.js :

     这是最后一步了。
    在解决了模块依赖模块查找的问题之后,我们将会得到一个依赖关系对象depTree,此对象完整地描述了以下信息:都有哪些模块,各个模块的内容是什么,他们之间的依赖关系又是如何等等。具体的结构如下

    {
        "modules": {
            "/Users/youngwind/www/fake-webpack/examples/simple/example.js": {
                "id": 0,
                "filename": "/Users/youngwind/www/fake-webpack/examples/simple/example.js",
                "name": "/Users/youngwind/www/fake-webpack/examples/simple/example.js",
                "requires": [
                    {
                        "name": "a",
                        "nameRange": [
                            16,
                            19
                        ],
                        "id": 1
                    },
                    {
                        "name": "b",
                        "nameRange": [
                            38,
                            41
                        ],
                        "id": 2
                    },
                    {
                        "name": "c",
                        "nameRange": [
                            60,
                            63
                        ],
                        "id": 3
                    }
                ],
                "source": "let a = require('a');\nlet b = require('b');\nlet c = require('c');\na();\nb();\nc();\n"
            },
            "/Users/youngwind/www/fake-webpack/examples/simple/a.js": {
                "id": 1,
                "filename": "/Users/youngwind/www/fake-webpack/examples/simple/a.js",
                "name": "a",
                "requires": [],
                "source": "// module a\n\nmodule.exports = function () {\n    console.log('a')\n};"
            },
            "/Users/youngwind/www/fake-webpack/examples/simple/b.js": {
                "id": 2,
                "filename": "/Users/youngwind/www/fake-webpack/examples/simple/b.js",
                "name": "b",
                "requires": [],
                "source": "// module b\n\nmodule.exports = function () {\n    console.log('b')\n};"
            },
            "/Users/youngwind/www/fake-webpack/examples/simple/node_modules/c.js": {
                "id": 3,
                "filename": "/Users/youngwind/www/fake-webpack/examples/simple/node_modules/c.js",
                "name": "c",
                "requires": [],
                "source": "module.exports = function () {\n    console.log('c')\n}"
            }
        },
        "mapModuleNameToId": {
            "/Users/youngwind/www/fake-webpack/examples/simple/example.js": 0,
            "a": 1,
            "b": 2,
            "c": 3
        }
    }
    

     打包优化

      使用了react全家桶之后,打包出的bundle.js是非常大的, 所以对之进行优化是十分有必要的。

    (1)、使用压缩插件,如下:

      在webpack.config.js中进行配置下面的代码:

        plugins: [
           new webpack.optimize.UglifyJsPlugin({
             compress: {
               warnings: false
             }
           })
         ]

      这样打包出来的文件可以从5M减少到1.7左右。 

     

    (2)、开发过程中使用 webpack-dev-server.

      我们当然可以每次使用打包出来的文件,但是更好的做法是将不把文件打包出来,然后从硬盘中获取,而是直接打包到内存中(即webapck-dev-server的作用),这样,我们就可以直接从内存中获取了,好处就是速度很快。 显然内存的读取速度是大于硬盘的。 

    展开全文
  • Webpack:从野战到入门

    2018-08-18 12:43:28
    Vue-cli 中有集成Webpack,所以之前做一个Vue 的项目的时候也了解并使用了Webpack 的部分功能。实战中用到的东西永远只是理论的冰山一角,而且像我们这种习惯使用hack 方法去解决问题的程序员写起代码来顶多只能称得...
  • webpack学习之路—入门篇1、什么是webpack?2、webpack的安装3、webpack的核心4、实例配置 1、什么是webpack? 首先是webpack对自己阐述:本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module ...
  • 研究了两天webpack的使用,刚开始看书,但是书不太适合我,很多细节流程没讲明白,于是乎我就上网搜索,综合了很多大神的帖子,摸索了两天,终于弄清楚其中所有的流程,细节,放在这里进行分享,也防止自己忘记了还...
  • webpack基本用法

    2018-09-02 11:02:04
    网页中常见的静态资源: JS:.js .jsx .coffee .ts(TypeScript 类c#语言) CSS:.css .less .sass .scss Images:.jpg .jpeg .png .gif .bmp 字体文件(Fonts): .svg .ttf .eot .woff .woff2 ...
  • 从我仅公司那天起,公司就一直在用webpack,这是一个前端自动打包工具,但我以前从来没接触过,不过幸好我聪明机智,天赋异禀,倒是能上手用,只不过有些配置还是看不懂,于是,我就趁着项目空闲时间好好研究了一下...
  • Webpack是目前基于React和Redux开发的应用的主要打包工具。我想使用Angular 2或其他框架开发的应用也有很多在使用Webpack。 ...当我第一次看到Webpack的...经过一段时间的尝试之后我认为这是因为Webpack只是使用了比
  • webpack 常用配置

    2019-12-09 15:52:53
    一、前言 webpack是目前前端开发中最流行的打包工具,不仅能实现 JS 打包,还能...使用时需要安装 webpackwebpack-cli 、webpack-dev-server三个基础模块。 二、常用 webpack 配置如下 module.exports = { ...
  • 什么是 webpackwebpack是近期最火的一款模块加载器兼打包工具,它能把各种资源,例如JS(含JSX)、coffee、样式(含less/sass)、图片等都作为模块来使用和处理。 我们可以直接使用 require(XXX) 的形式来...
  • Webpack介绍

    2018-12-09 15:33:55
    WebPack是前端资源模块打包机,主要分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。 WebPack的优点: 模块...
  • webpack和gulp的区别

    2019-08-07 11:08:36
    webpack和gulp的区别是什么? 这是一个前端面试经常会有的面试题,当然也是很多人的疑问,那下面就简单说一下吧! Gulp: gulp 致力于 自动化和优化 你的工作流,它是一个自动化你开发工作中 痛苦又耗时任务 的工具...
  • webpack4.x开发环境配置

    2019-10-13 14:00:46
    写这篇文章的初衷在于,虽然网络上关于webpack的教程不少,但是大多已经过时,由于webpack版本更新后许多操作变化很大,很多教程的经验已经不适合。当我们使用npm安装webpack时,若不指定webpack的版本,将默认安装...
  • 由此而来的还有之前webpack3.x的项目如何迁移到新的webpack版本,本文就一个新的vue-cli创建的基于webpack的项目进行迁移。 题外话:不要看0配置是很有噱头,基本是不能满足大部分用户啊的需求,不过加...
  • 一、问题描述在安装webpack时,输入webpack -v提示我需要安装webpack-cli,如图:二、解决因为在webpack 3中,webpack本身和它的CLI以前都是在同一个包中,但在第4版中,他们已经将两者分开来更好地管理它们。...
  • 更好的维护代码,把 webpack.config.js 拆分成三个部分: 公 共 配 置 : 把 开 发 和 生 产 环 境 需 要 的 配 置 都 集 中 到 公 共 配 置 文 件 中 , 即 webpack.common.js 开发环境配置:把开发环境需要的相关...
  • 背景:最近收到很多童鞋的问题:gulp和webpack 什么关系,是一种东西吗?可以只用gulp,不用webpack吗 或者反过来? 基于此问:我简单归结了一下区别和概念,让需要的同学阅读理解,从而不把时间浪费到这种模糊不清...
1 2 3 4 5 ... 20
收藏数 122,775
精华内容 49,110
关键字:

webpack