2 - 《webpack的基本了解和配置》

86 阅读8分钟

webpack介绍

安装

npm install webpack webpack-cli --save-dev

loader

enforce  分类

  • pre 前置
  • normal 正常
  • inline 行内也被称为内联
  • post 后置

mode

  1. --mode process.env.NODE_ENV 只是在模块内可用,在node环境中不可用
  2. --env
    node取不到 模块内也取不到
    只能在配置文件的参数中拿到
  3. cross-env 设置环境变量
    NODE_ENV 真正的操作系统的环境变量
    node里可以取到,其他地方都取不到 配置文件参数 模块内都取不到

windows

  • set key=value
  • echo %key%
    mac
  • export key=value

打包文件

  1. 如果文件名不一样,不会直接覆盖,一样的话就会覆盖,想删除老的就需要手动清除
  2. 不想手动清除 clean-webpack-plugin
  3. 模块id根源文件的路径有关,其实就是源文件相对于根目录的相对路径,跟打包后的文件没有关系

babel

npm i babel-loader @babel/core @babel/preset-env @babel/preset-react -D

npm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
  • 默认情况下有些代码webpack是不认识的 比如jsx   有些代码是浏览器不兼容的 es6 es7
  • 把es6、es7编译成es5
  • 把React编译成es5
  • 靠babel-loader
  • babel-loader只是一个转换函数,并不能识别JS的语法,也不知道如何转换
  • 得认识JS代码,知道如何把新代码转换成老代码
  • @babel/core 它是babel的核心模块,它认识JS代码,能够识别JS代码,不知道如何转换写法
  • babel插件知道如何把新语法转成老语法,每个插件对应一个语法,比如说箭头函数
  • plugin-transform-arrow-functions 可以把箭头函数转成普通函数
  • ES6/ES7语法是很多的
  • 把插件打包成preset预设,预设就是插件的集合
  • es6->es5的所有的插件打成一个包   @babel/preset-env

eslint

webpack调用eslint eslint会读配置文件
airbnb是规则的集合 现在是eslint:recommended
最终都是给eslint来读取规则

npm i eslint eslint-loader babel-eslint -D

LF
line feed 换行
CRLF
carriage return line feed 回车换行

windows 换行符 \r\n \r回车 \n换行
linux里换行符\n 没有\r
来自于老式打印机

sourcemap

  • eval 使用eval包裹模块代码 sourcemap信息分开了 eval("var a = 1 //# sourceURL=[module]\n) 使用sourcemap的缓存,可以提升重复构建的速度 如果你生成的是一个单独的map文件,所有的模块的map信息耦合在一起的。 如果一个模块发生变化,整表map文件都要重新计算生成 eval 每个模块的map文件单独存放,可以单独缓存 有一个模块发生变更,只需要重新计算着一个模块的map信息就可以了
  • source-map 产生.map文件
  • cheap 不包含列信息也不包含loader的sourcemap
  • module 包含loader的sourcemap(比如jsx to js,babel的sourcemap),否则无法定义源文件
  • inline 将.map文件作为DataURL嵌入,不单独生成.map文件

组合规则

[inline-[hidden-|eval-][nosources-][cheap-[module-]]source-map
  • source-map 单独在外部生 成完整的sourcemap文件,并旦在目标文件里建立关联,能提示错误代码的准确原始位置
  • inline-source-map 以base64格式内联在打包后的文件中,内联构建速度更快,也能提示错误代码的准确原始位置
  • hidden-source-map 会在外部生成sourcemap文件,但是在目标文件里没有建立关联,不能提示错误代码的准确原始位置
  • eval-source-map 会为每一个模块生成一个单独的sourcemap文件进行内联,并使用 eval 执行
  • rosources-source-map 也会在外部生成sourcemap文件,能找到源始代码位置,但源代码内容为空
  • cheap-source-map 外部生成sourcemap文件,不包含列和loader的map
  • cheap-module-source-map 外部生成sourcemap文件,不包含列的信息但包含loader的map

最佳实践

开发环境

  • 我们在开发环境对sourceMap的要求是:速度快,调试更友好
  • 要想谏度快推荐
    eval-cheap-source-map
  • 如果想调试更友好 cheap-module-source-map
  • 折中的选择就是 eval-source-map

生产环境

  • 首先排除内联,因为一方面我们了隐藏源代码,另一方面要减少文件体积
  • 要想调试友好 sourcemap>cheap-source-map/ cheap-module--source-mapshidden-source-map/ nosources-sourcemap
  • 要想速度快 优先选择 cheap
  • 折中的选择就是 hidden-source-map

调试代码

测试环境调试

  • source-map-dev-tool-plugin实现了对 source map 生成,进行更细粒度的控制
  • filename (string):定义生成的source map 的名称(如果没有值将会变成 inlined)。
  • append (string):在原始资源后追加给定值。通常是 #source MappingURL 注释。(ur] 被替换成 source map 文件的URL
  • 市面上流行两种形式的文件指定,分别是以 。 和# 符号开头的,® 开头的已经被废奔

开发服务器

webpack-dev-middleware

webpack-dev-middleware就是在 Express 中提供webpack-dev-server 静态服务能力的一个中间件

npm install webpack-dev-middleware -save-dev

const express = require('express');
const app = express ();
const webpack = require('webpack');
const webpackDevMiddleware = require( 'webpack-dev-middleware');
const webpackOptions = require('./webpack.config');
webpackOptions.mode = 'development';
const compiler = webpack(webpackOptions);
app.use(webpackDevMiddleware(compiler, (}));
app.listen (3000);
  • webpack-dev-server的好处是相对简单,直接安装依赖后执行命令即可
  • •而使用 webpack-dev-middleware 的好处是可以在既有的 Express 代码基础上快速添加 webpack-dev-server 的功能,同时利用 Express 来根据需要添加更多的功能,如mock 服务、代理 API 请求等

生产环境

提取css

  • 因为CSS的下载和JS可以并行,当一个HTML文件很大的时候,我们可以把CSS单独提取出来加载

安装

  • mini-css-extract-plugin

npm install mini-css-extract-plugin --save-dev

基本配置

// webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const EslintWebpackPlugin = require("eslint-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

// const FilemanagerPlugin = require('filemanager-webpack-plugin')
// const webpack = require('webpack')

console.info("🚀 ~ log:process.env.NODE_ENV ----->", process.env.NODE_ENV);

module.exports = {
  mode: "development",
  // 生成单独的source-map,但是不在main.js里建立关联
  devtool: false, // 不生成sourcemap,关掉内部生成sourcemap的逻辑,我要自己精细化控制生成的过程
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"), // 会成为默认的静态文件根目录
    filename: "main.js",
    publicPath: "/", // 加载产出文件的时候的路径前缀
  },
  // watch:true, // 默认是false,监听文件的变化
  // watchOptions:{
  //   ignored:/node_modules/, // 忽略监控的文件夹
  //   aggregateTimeout:300, // 默认300ms,防抖
  //   poll:1000 // 默认1000ms,轮询
  // },
  // 内部就是一个express服务器
  devServer: {
    port: 8080, // 配置http服务预览的端口号,如果不设置默认就是8080
    // open:true, // 编译成功后会自动打开浏览器
    compress: true, // 是否启动gzip压缩
    static: path.resolve(__dirname, "public"), // 额外的静态文件的根目录
    // onBeforeSetupMiddleware(devServer){  // express()
    //   if (!devServer) {
    //     throw new Error('webpack-dev-server is not defined');
    //   }
    //   devServer.app.get('/api/users', (req, res) => {
    //     res.json([
    //       {
    //         id:1,
    //         name: 'jerry',
    //         age: 18
    //       },
    //       {
    //         id:2,
    //         name: 'cherry',
    //         age: 24
    //       }
    //     ])
    //   })
    // }
    // proxy:{
    //   '/api':{
    //     target:"http://localhost:3000",
    //     pathRewrite:{
    //       '^/api':''
    //     }
    //   }
    // }
  },
  // 配置外部模块
  externals: {
    jquery: "jQuery",
    lodash: "_",
  },
  resolve:{
    alias:{
      "@":path.resolve(__dirname,'public')
    }
  },
  module: {
    // use是使用哪些loader进行转换,顺序从右往左
    // 最右边的loader接收源文件,最左侧的loader返回一个js脚本
    // 为什么不用一个loader干所有的事情,而是用小loader组合起来,单一原则,每个loader只做单一的一件事情
    rules: [
      // {
      //   test:/\.js$/,
      //   loader:'eslint-loader',
      //   enforce:'pre', // 前置 优先执行
      //   options:{ fix:true }, // 如果发现有问题自动修复
      //   exclude:/node_modules/ // 排除node_modules目录
      // },

      // 他可以把一个变量放在全局对象上
      // {
      //   test:require.resolve('lodash'),
      //   loader:"expose-loader",
      //   options:{
      //     exposes:{
      //       globalName:'_', // 放的全局变量名
      //       override:true   // 如果原来这个变量名有值,是否覆盖
      //     }
      //   }
      // },
      {
        test: /\.js$/,
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-env", "@babel/preset-react"],
              plugins: [
                ["@babel/plugin-proposal-decorators", { legacy: true }],
                [
                  "@babel/plugin-proposal-private-property-in-object",
                  { loose: true },
                ],
                ["@babel/plugin-proposal-private-methods", { loose: true }],
                ["@babel/plugin-proposal-class-properties", { loose: true }],
              ],
            },
          },
        ],
      },
      {
        test: /\.css$/,
        // less-loader less转换成css css-loader 处理import和url的 style-loader把css编程js脚本的
        // use:['style-loader','css-loader','postcss-loader'],
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              modules: false,
              url:true,
              import:true
            },
          },
          "postcss-loader",
        ],
      },
      {
        // 以.module.css结尾的文件使用css模块化
        test: /\.module\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              // modules: true, // 开启css模块化
              modules: {
                mode: "local",
                localIdentName: "[path][name]__[local]--[hash:base64:5]", // 自定义css模块化的类名
              },
            },
          },
          "postcss-loader",
        ],
      },
      {
        test: /\.less$/,
        // use:['style-loader','css-loader','postcss-loader','less-loader']
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "less-loader",
        ],
      },
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "sass-loader",
        ],
        // use:['style-loader','css-loader','postcss-loader','sass-loader']
      },
      {
        test: /\.(jpg|png|gif|bmp|svg)$/,
        // use:[
        // file-loader可以把src目录里依赖的图片文件拷贝到目标目录里去,文件名一般为新的hash值
        //   {
        //     loader:"url-loader",
        //     options:{
        //       esModule:false,
        //       name:`[hash:8].[ext]`,
        //       limit:1024*8, // 如果文件太小,比如雪碧图,不需要拷贝文件,也不需要发http请求了,只需要把文件编程base64字符串,内嵌到页面中
        //       outputPath:'images', // 指定输出的目录
        //       publicPath:'images'  // 指定引入的时候的目录
        //     }
        //   }
        // ],

        // type: 'javascript/auto'

        // type: "asset/resource", // 替代以前的file-loader
        // generator: {    // 生成的文件目录
        //   publicPath: "images/",
        //   outputPath: "images",
        // },

        // type: "asset/inline", // 替代以前的url-loader

        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024, // 8kb
          },
        },
      },
    ],
  },
  plugins: [
    new EslintWebpackPlugin({
      extensions: ["js", "jsx"],
      fix: true,
    }),
    new HtmlWebpackPlugin({
      template: "./index.html",
    }),
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: ["**/*"],
    }),

    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, "public"),
          to: path.resolve(__dirname, "dist/public"),
        },
      ],
    }),

    new MiniCssExtractPlugin({
      filename: "css/main.css",
    }),

    // new webpack.ProvidePlugin({
    //   _:'lodash'
    // })

    // 由此插件来控制sourcemap的生成
    // new webpack.SourceMapDevToolPlugin({
    //   // 向输出的文件里添加的映射文本
    //   append:`\n//# sourceMappingURL=http://127.0.0.1:8081/[url]`,
    //   filename:`[file].map`, // main.js sourcemap文件名叫 main.js.map
    // }),

    // 将要发布测试环境
    // 生成sourcemap文件,但是sourcemap只放在本机,并不部署到测试环境
    // new FilemanagerPlugin({
    //   events:{
    //     onEnd:{
    //       copy: [
    //         { source: './dist/**/*.map', destination: path.resolve(__dirname,"maps") },
    //       ],
    //       delete: ['./dist/**/*.map'],
    //     }
    //   }
    // }),

    // new webpack.DefinePlugin({
    //   "process.env.NODE_ENV":JSON.stringify(process.env.NODE_ENV) // '"development"'
    // }),
  ],
};