webpack详解

Webpack详解

认识webpack

  • 从本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具

image-20210813000144374

  • 打包工具:
    • grunt/gulp/webpack/rollup等
  • 前端模块化:
    • 前面的学习中提到了为什么使用前端模块化,也提到了目前使用前端模块化的一些方案:
      • AMD、CMD、CommonJS、ES6
    • 学习了webpack之后,我们就不止能使用ES6,webpack可以作为这些方式的底层支撑,并且会将其转化成浏览器能识别的代码,也能将ES6语法转为ES5的语法,处理各模块之间的依赖关系。
      • 而且不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当作模块来使用
  • webpack依赖于node环境
    • node环境为了支撑执行很多代码,其中必须依赖各种的包,node手动管理这些包很麻烦,所以下载node时会安装npm工具(npm packages manager)。

webpack的安装

  • 全局安装webpack(这里先指定版本号3.6.0,因为vue cli2依赖该版本)

    • npm install webpack@3.6.0 -g
      
  • 局部安装webpack

    • --save-dev 是开发时依赖,项目打包后不需要继续使用。

    • cd 对应目录
      npm install webpack@3.6.0 --save-dev
      
  • 为什么全局安装后,还需要局部安装?

    • 在终端直接执行webpack命令,使用的是全局安装的webpack
    • 当在package.json中定义了scripts脚本时,这个脚本中包含了webpack命令,那么使用的是局部webpack(详见webpack的配置)

webpack的起步

  • 新建项目 -> 创建文件夹 -> src 和 dist文件夹

    image-20210812012118963

    • dist:distribution(发布),将来直接发布这个文件夹即可。
    • src:源码

    • cd到01-webpack的起步目录下,执行

      webpack ./src/main.js ./dist/bundle.js
      
      • 代码使用的是CommonJS的导出导入方式,直接运行代码浏览器不会识别

      • 在index.html中,先不引用js文件,当webpack生成后再引用生成的文件即可

        <script src="./dist/bundle.js"></script>
        
      • 在命令中,只需引入main.js即可,会自动处理引用的文件,并将生成的文件放到dist目录下的bundle.js中

    • image-20210812020530071

    • 浏览器resource中可以看到:已经成了编译后的代码(本来浏览器可能不识别这些代码,这些编译后的代码就可以作为一个支撑,让浏览器识别)

    • 还支持同时使用不同的导出、导入方式:

      main.js

      import {name, age} from "./info";
      const {add, mul} = require('./mathUtils.js')
      console.log('Hello World');
      console.log(add(20, 30));
      console.log(mul(20, 30));
      
      console.log(name);
      console.log(age);
      

      info.js(新增,之前的mathUtils.js为CommonJS导出方式)

      export const name = 'Elian'
      export const age = 18
      

      重新执行package命令后,会同时打印结果:

      image-20210812021649349

webpack的配置

  • 每次执行命令webpack ./src/main.js ./dist/bundle.js很麻烦,所以对webpack进行一些配置

    • 首先,一旦当前项目准备用node中的一些东西时,就先在当前项目路径下执行一下命令进行初始化,引入依赖,在当前文件下创建包文件

      npm init
      
    • 终端中填写packaeg name等信息,注意中文以及符号问题

      image-20210812225107814

      回车即可

    • 这时会在当前项目下创建package.json文件(以下,文件说明)

      image-20210812230438225

    • 创建webpack.config.js

      const path = require('path')  // CommonJS语法,导入path
      module.exports = {            // CommonJS语法,导出
        entry: './src/main.js',	  // 入口
        output: {					  // 出口
          path: path.resolve(__dirname, 'dist'), // 动态获取绝对路径 __dirname:当前路径 dist:要生成文件的路径
          filename: 'bundle.js'
        }
      }
      
    • 现在只要在terminal中执行

      webpack
      

      即可。项目打包重新运行,输出结果

  • 当命令很长的时候,我们需要配置npm run *命令指向特定命令

    • 打开package.json文件,编辑"script"

      image-20210812231555174

      这样在执行:npm run build时,就会执行webpack命令了

    • 这样配置的原因:

      1. 可能一条命令很长,而我们要在测试时多次执行这条命令,现在无论多长的命令都可以通过npm run *的方式来运行

      2. 我们配置了全局的webpack,有时候全局的webpack可能时4以上的版本,当前项目可能使用的是3.X的版本,会找错版本导致打包之后出现问题,这时,会在本地进行安装package,之后执行npm run build 就会先在本地找webpack了。而我们要在cmd,terminal窗口中执行webpack命令,它只会在全局去找这个命令。

        在项目路径下执行

        npm install webpack@3.6.0 --save-dev
        

        --save-dev:是指开发时依赖,因为很多时候我们打完包以后,就不用webpack了,所以只需要安装开发时依赖即可。

    • 执行完后会在package.json中多出:

      开发时依赖

      image-20210812233032573

loader的使用

  • loader是webpack中一个非常核心的概念。
  • 已经知道webpack用来:
    • 处理js之间相关的模块引用,依赖。
    • 但是除了js还要加载css,图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。
    • 对于webpack本身的能力来说,对于这些转化是不支持的,这时给webpack扩展对应的loader就可以啦

loader使用过程

  • 步骤一:通过npm安装需要使用的loader,不同的文件需要不同的loader
  • 步骤二:在webpack.config.js中的modules关键字下面进行配置
  1. 重构代码:

    image-20210813001913839

  2. 在main.js中添加对normal.css的引用,webpack对loader的支持在

    里讲的很详细

    npm install --save-dev css-loader@2.0.2
    
  3. 在webpack.config.js中配置:

    const path = require('path')
    module.exports = {
      entry: './src/main.js',
      output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
      },
      module: {		// 新增
        rules: [
          {
            test: /\.css$/,
            use: [ 'css-loader' ]
          }
        ]
      }
    }
    

    打开浏览器发现界面并没有变红,这里说明:css-loader只负责加载文件,不负责渲染样式,要想渲染要是还需加载一个loader

  4. 安装style-loader

    npm install style-loader@0.23.1 --save-dev
    

    配置webpack.config.js

    use: [ 'style-loader', 'css-loader' ]
    
  5. 再次运行npm run build,刷新浏览器,发现背景已经改变

  6. 注意:loader加载时是从右向左读取的

其他类型文件的loader

  • less文件:

    • 引入loader

      npm install less-loader@4.1.0 --save-dev less@3.9.0
      
    • 配置webpack.config.js

      rules: [
            {
              test: /\.css$/,
              use: [ '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
              }]
            }
          ]
      
    • 说明:less-loader将文件加载到DOM中,less将文件转成css

      special.less

      @fontSize: 50px;
      @fontColor: orange;
      
      body {
        font-size: @fontSize;
        color: @fontColor;
      }
      

      并在main.js中引入

  • 图片:

    • 引入loader

      npm install --save-dev url-loader@1.1.2
      
    • 配置webpack.config.js

      rules: [
            {
              test: /\.css$/,
              use: [ '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: /\.(png|jpg|gif)$/,
              use: [
                {
                  loader: 'url-loader',
                  options: {
                    limit: 14000
                  }
                }
              ]
            }
          ]
      
    • 说明:options中limit的作用,最小的缓冲区,如果文件大于这个值,必须引入另一个file-loader,如果小于这个值,那么会将图片转成base64,这个file-loader直接下载即可,不需要针对loader进行特别的配置。

      npm install file-loader@3.0.1 --save-dev
      
      • 但是,之前没有loader时,并且文件大小小于配置中的大小时会转成base64的字符串形式,这个loader会将文件重新命名后加载到dist文件中,这时就要配置一个路径,让这原先的路径能找到dist下的这个文件。

      • 配置webpack.config.js中的output

        image-20210813225740513

      • 添加publicPath路径,这个属性的目的就是为了在url前面加上一个对应的路径。

      • 同时,dist下生成的文件是个hash32位的命名,很乱,不知道原图片是什么,这个时候还需对名字进行配置:

        image-20210813231813626

        • 最终生成的文件:

          image-20210813232141422

  • ES6转ES5的babel的loader:

    npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
    

    配置webpack.config.js

    image-20210813234502496

webpack中配置Vue

引入vue.js

  • 后续项目中我们会使用Vuejs进行开发,而且会以特殊的文件来组织vue的组件

  • 安装依赖

    • 因为后续发布的时候也会使用vue,所以并不是开发时依赖

      npm install vue --save
      
  • 然后就可以按照之前的方式使用vue了。

    // index.html中添加vue挂载的div
    <div id="app">
      {{message}}
    </div>
    
    // main.js中引入vue,并copy我们之前的代码过来
    import vue from 'vue';
    
    const app = new Vue({
      el: '#app',
      data: {
        message: 'Hello World'
      }
    })
    
    • build运行之后发现报错了

      image-20210814003015388

      这是因为:

      vue在构建最终的发布版的时候,构建了两类发布版本:

      1. runtime-only --在这个版本中不允许有template
      2. runtime-compiler --在这个版本中允许有template,因为有compiler代码用于编译template
    • 当前解决办法

      • 在webpack.config.js中添加resolve:

        image-20210814004039127

el和template的区别:

  • 正常运行以后,我们来考虑一个问题:

    • 如果我们希望将data中的数据显示在界面中,就必须修改index.html
    • 如果我们后面自定义了组件,也必须修改index.html来使用组件
    • 但是vue作为单页面复用技术,并须希望手动的来频繁的修改,是否可以做到呢?
  • 定义template属性:

    • 当index.xml中和Vue实例中同时定义了同一个div,就会将template中的div替换到html中的div去

      image-20210814005726962

对template进行优化

  • 第一次优化:

    image-20210814011750300

    抽取模板

    在vue中定义components

    在vue的template中引用

  • 第二次优化:

    image-20210814013327017

    在src目录下新建vue文件夹,创建app.js并导出一个default的模板

    定义components,引入App

    在main.js中导入,并在template中引用

  • 第三次优化

    • 在vue目录下创建vue componen文件

      <template>
        <div id="app">
          <h2 class="message">{{message}}</h2>
          <button @click="btnClick">按钮</button>
          <h2>{{name}}</h2>
        </div>
      </template>
      
      <script>
      export default {
        name: "App",
        data() {
          return {
            message: 'Hello World',
            name: 'Elian'
          }
        },
        methods: {
          btnClick() {
            console.log('按钮被点击了');
          }
        }
      }
      </script>
      
      <style scoped>
        .message {
          color: orange;
        }
      </style>
      
    • 删除刚刚创建的app.js即可,并在main.js中引用即可

      import Vue from 'vue';
      import App from './vue/app';	// 新的引用
      
      new Vue({
        el: '#app',
        template: `<App/>`,
        components: {
          App
        }
      })
      

      这时进行build会报错,原因是没有处理.vue的loader

  • 安装loader(开发必用

    • 安装:vue-loader 和 vue-tempalte-compiler[vue-loader最好用13.0.0以上的版本,避免安装其他插件]

      npm install --save-dev vue-loader vue-template-compiler
      
    • 配置:

      {
        test: /\.vue$/,
        use: ['vue-loader']
      }
      
    • vue-loader在14以上的版本中会出现需要另外安装插件的问题

      image-20210814021811792

plugin的使用

认识plugin

  • plugin是什么?
    • plugin(插件),通常是对现有 的架构进行扩展
    • webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等。
  • loader和plugin的区别?
    • loader主要用于转换某些类型的模块,他是一个转换器
    • plugin是插件,它是对webpack本身的扩展,是一个扩展器
  • plugin的使用过程:
    • 步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)
    • 步骤二:在webpack.config.js中的plugins中配置插件

让webpack更加好用的plugins

  • 添加版权的Plugin

    • 这个插件属于内置插件,不需要再install,直接进行引用,并添加plugins属性并添加要输入版权的名称

      image-20210814133800070

  • 打包html的plugin

    • 目前我们的index.html文件是存放在项目的根目录下的。

      • 我们知道,在真实发布项目时,发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也就没有意义了。
      • 所以我们需要将index.html文件打包到dist文件夹中,这个时候就可以使用HtmlWebpackPlugin插件
    • HtmlWebpackPlugin插件可以为我们:

      • 自动生成一个index.html文件(可以指定模板来生成)
      • 将打包的js文件,自动通过script标签插入到body中
    • 安装HtmlWebpackPlugin插件

      npm install html-webpack-plugin --save-dev
      
    • 使用插件,修改webpack.config.js文件中plugins部分的内容如下:

      • 这里的template表示根据什么模板来生成index.html

      • 另外,我们休要删除之前在output中添加的publicPath属性删除,否则插入的script标签中的src可能会有问题。

        image-20210814174700550

      • index.html中,还是需要一个id为app的div,但是不用再引入bundle.js了,会自动引入dist下的bundle.js

  • js压缩的Plugin

    • 在项目发布之前,我们必然需要对js等文件进行压缩处理

      • 这里,我们就对打包的js文件进行压缩

      • 我们使用一个第三方的插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和CLI2保持一致

        npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
        
    • 修改webpack.config.js文件,使用插件:

      const uglifyJsPlugin = require('uglifyjs-webpack-plugin')
      ----
      plugins: [
            ...
            new uglifyJsPlugin()
        ]
      

搭建本地服务器

  • webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。

  • 不过它是一个单独的模块,在webpack中使用之前要先安装他:

    npm install --save-dev webpack-dev-server@2.9.1
    
  • devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:

    • cnotentBase:为那一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
    • port:端口号
    • inline:页面实时刷新
    • historyApiFallback:在SPA页面中,依赖HTML5的history模式
  • webpack.config.js文件和package.json配置如下:

    image-20210814222806634

  • 执行

    npm run serve
    

抽离webpack.config.js

  • 为什么要抽离?

    • 像我们最后一个配置devServer,只有在开发的时候才用到,真正发布项目后,是不会使用这个devServer的,这时我们就要配置一个开发时运行的和一个发布时运行的。

      1. 项目下创建src的同级目录build文件夹

      2. 安装webpack-merge

        npm install webpack-merge@4.1.5 --save-dev
        
      3. 创建base.config.js、dev.config.js、prod.config.js文件,将公共的配置放在base.config.js中,而开发时的配置放在dev.config.js中,生产时的配置文件放在prod.config.js中。

        base.config.js

        const path = require('path')
        const webpack = require('webpack')
        const HtmlWebpackPlugin = require('html-webpack-plugin')
        
        module.exports = {
          entry: './src/main.js',
          output: {
            path: path.resolve(__dirname, '../dist'), // 修改路径为../dist
            filename: 'bundle.js'
          },
          module: {
            rules: [
              {
                test: /\.css$/,
                use: ['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: /\.(png|jpg|gif)$/,
                use: [
                  {
                    loader: 'url-loader',
                    options: {
                      limit: 1400,
                      name: 'img/[name].[hash:8].[ext]'
                    }
                  }
                ]
              },
              {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                  loader: 'babel-loader',
                  options: {
                    presets: ['es2015']
                  }
                }
              },
              {
                test: /\.vue$/,
                use: ['vue-loader']
              }
            ]
          },
          resolve: {
            extensions: ['.js','.css','.vue'],
            alias: {
              'vue$': 'vue/dist/vue.esm.js'
            }
          },
          plugins: [
            new webpack.BannerPlugin('最终版权归Elian所有'),
            new HtmlWebpackPlugin({
              template: `index.html`
            })
          ]
        }
        

        dev.config.js

        const baseConfig = require('./base.config')
        const webpackMerge = require('webpack-merge')
        
        module.exports = webpackMerge(baseConfig, {
          devServer: {
            contentBase: '../dist', // 要监听的文件夹
            inline: true           // 实时监听
          }
        })
        

        prod.config.js

        const baseConfig = require('./base.config')
        const uglifyJsPlugin = require('uglifyjs-webpack-plugin');
        const webpackMerge = require('webpack-merge')
        
        module.exports = webpackMerge(baseConfig, {
          plugins: [
            new uglifyJsPlugin()
          ]
        })
        
      4. 修改base.config.js下的path路径为../dist,修改package.json文件,添加build,修改serve

        image-20210814232655570

总结:

创建一个简单的模板:

  1. 目录结构:

    image-20210815001715634

  2. base.config.js代码:

    const path = require('path')  // node语法,导入path
    const webpack = require('webpack')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {            // CommonJS语法,导出
      entry: './src/main',
      output: {
        path: path.resolve(__dirname, '../dist'), // 动态获取绝对路径 __dirname:当前路径 dist:要生成文件的路径
        filename: 'bundle.js'
        /*指定文件去dist根下找,如果把index.html放到dist下,就是绝对路径
        * 不需要配置这个了*/
        /*publicPath: 'dist/'*/
      },
      module: {
        rules: [
          {
            test: /\.css$/,
            use: ['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: /\.(png|jpg|gif)$/,
            use: [
              {
                loader: 'url-loader',
                options: {
                  // 当加载的图片,小于limit时,会将图片编译成base64字符串形式
                  // 当加载的图片,大于limit时,需要使用file-loader模块进行加载
                  limit: 1400,
                  // [name]:原名 [hash:8]取hash8位 [ext]继承原格式
                  name: 'img/[name].[hash:8].[ext]'
                }
              }
            ]
          },
          {
            test: /\.js$/,
            // 排除node_modules等文件夹下的文件转换
            exclude: /(node_modules|bower_components)/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: ['es2015']
              }
            }
          },
          {
            test: /\.vue$/,
            use: ['vue-loader']
          }
        ]
      },
      // 目的是为了在执行 import Vue from 'vue' 时
      // 先来看有没有将vue指向别的文件
      // 这时就不会去找默认的文件了,而是来找这个指定的文件
      resolve: {
        extensions: ['.js','.css','.vue'],
        alias: {
          'vue$': 'vue/dist/vue.esm.js'
        }
      },
      plugins: [
        new webpack.BannerPlugin('最终版权归Elian所有'),
        new HtmlWebpackPlugin({
          template: `index.html`
        })
      ]
    }
    
  3. dev.config.js代码

    const baseConfig = require('./base.config')
    const webpackMerge = require('webpack-merge')
    
    module.exports = webpackMerge(baseConfig, {
      devServer: {
        contentBase: '../dist', // 要监听的文件夹
        inline: true           // 实时监听
      }
    })
    
  4. prod.config.js代码

    const baseConfig = require('./base.config')
    const uglifyJsPlugin = require('uglifyjs-webpack-plugin');
    const webpackMerge = require('webpack-merge')
    
    module.exports = webpackMerge(baseConfig, {
      plugins: [
        new uglifyJsPlugin()
      ]
    })
    
  5. index.html模板

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
    </head>
    <body>
    
    <div id="app">
    </div>
    
    </body>
    </html>
    
  6. App.vue代码(可以等完成npm install之后再创建)

    <template>
      <div id="app">
        <h2 class="message">{{message}}</h2>
        <button @click="btnClick">按钮</button>
        <h2>{{name}}</h2>
        <h2>{{name}}</h2>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
      data() {
        return {
          message: 'Hello World',
          name: 'Elian'
        }
      },
      methods: {
        btnClick() {
          console.log('按钮被点击了');
        }
      }
    }
    </script>
    
    <style scoped>
      .message {
        color: orange;
      }
    </style>
    
  7. main.js代码

    //使用vue进行开发
    import Vue from 'vue';
    import App from './vue/App.vue'
    
    new Vue({
      el: '#app',
      template: `<App/>`,
      components: {
        App
      }
    })
    
  8. package.json代码

    {
      "name": "template",
      "version": "1.0.0",
      "description": "vue project develop template",
      "main": "webpack.config.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack --config ./build/prod.config.js",
        "dev": "webpack-dev-server --open --config ./build/dev.config.js",
        "webpack": "webpack --version"
      },
      "author": "Elian",
      "devDependencies": {
        "babel-core": "^6.26.3",
        "babel-loader": "^7.1.5",
        "babel-preset-es2015": "^6.24.1",
        "css-loader": "^2.0.2",
        "file-loader": "^3.0.1",
        "html-webpack-plugin": "^3.2.0",
        "less": "^3.9.0",
        "less-loader": "^4.1.0",
        "style-loader": "^0.23.1",
        "uglifyjs-webpack-plugin": "^1.1.1",
        "url-loader": "^1.1.2",
        "vue-loader": "^13.0.0",
        "vue-template-compiler": "^2.5.21",
        "webpack": "^3.6.0",
        "webpack-dev-server": "^2.9.1",
        "webpack-merge": "^4.1.5"
      },
      "dependencies": {
        "vue": "^2.5.21"
      }
    }
    
  9. 所有配置完以后,在当前目录下cmd执行以下命令

    npm init
    
    npm install --legacy-peer-deps
    然后执行步骤6
    
    npm run dev/build
    
posted @ 2021-08-15 02:17  coderElian  阅读(153)  评论(0)    收藏  举报