webpack从配置到跑路v1

webpack 是一个现代JavaScript应用程序的静态模块打包器。
webpack处理应用程序时,会递归构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个bundle

核心概念

  • entry 入口
  • output 输出
  • loader 模块转换器,用于把模块原内容按照需求转换成新内容;
  • plugins 扩展插件,在webpack构建流程中的特定时机注入扩展逻辑,来改变构建结果或做你想要做的事情。

作用:模块化打包
webpack是一个模块打包工具,一个打包模块化JavaScript的工具,是工程化、自动化思想在前端开发中的体现。
webpack将项目的资源文件当成一个个模块,模块之间会有依赖关系,它会对这些有依赖关系的文件进行处理 -- 从入口模块出发,识别出模块之间的依赖关系(模块化导入语句),递归找出入口文件的所有依赖,将它们打包到一个或多个文件中(bundle)。

初始化项目

  • 新建一个目录, npm init -y
  • 安装核心依赖
    npm i webpack webpack-cli -D  // webpack4.x.x
    

局部安装的命令无法像全局安装那样直接使用,需要配合 npx,它会从当前项目的node_modules中查找,比如 npx webpack -v(查看版本号)
wepack V4.0.0 开始, webpack是开箱即用的,在不引入任何配置文件的情况下就可以使用。

  • 新建src/index.js
    class Animal {
        constructor(name) {
            this.name = name;
        }
    }
    const dog = new Animal('dog');
  • 构建:npx webpack --mode=development,默认 production 模式。development模式可以更好的查看打包后的代码。
(function(module, exports) {
eval("class Animal {\r\n    constructor(name) {\r\n  ......
})

默认输出目录和文件:dist/main.js更多默认配置在node_modules/webpack/lib/WebpackOptionsDefaulter.js

查看dist/main.js可知,src/index.js中的代码并没有被转译为低版本代码,这显然不是我们想要的。

babel-loader

loaderwebpack的四大核心之一,用于对源代码进行转换,而转换工具正是Babel
babel-loaderBabelwebpack的使用方式,可以将JS代码向低版本转换。

  • 安装Babel相关的依赖
    npm i @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
    npm i @babel/runtime @babel/runtime-corejs3 -S
    
    npm i babel-loader -D
    
  • 新建webpack的配置文件webpack.config.js
    // ! Webpack是基于NodeJS的,必须使用CommonJS规范导出一个对象
    module.exports = {
        module: {
            rules: [
                {
                    test: /\.js$/,
                    use: ['babel-loader'],
                    exclude: /node_modules/ //排除 node_modules 目录
                }
            ]
        }
    }
    
    建议给 loader 指定 includeexclude,有效提升编译效率
  • 新建Babel配置文件 .babelrc
    {
        "presets": ["@babel/preset-env"],
        "plugins": [
            ["@babel/plugin-transform-runtime", { "corejs": 3 }]
        ]
    }
    

重新编译,发现代码已经被Babel降级转换了。

如果不想创建.babelrc,还可以在 webpack.config.js 配置 Babel

use: {
    loader: 'babel-loader',
    options: {
        presets: ["@babel/preset-env"],
        plugins: [
            ["@babel/plugin-transform-runtime", { "corejs": 3 }]
        ]
    }
},

loader配置项

loader 需要配置在 module.rules 中,rules是一个数组。

  • 字段 test 表示匹配规则,对项目中符合规则的文件进行处理;
  • loader 的配置方式有多种
    • 只适用于一个 loader 的情况
      {
          test: /\.js$/,
          loader: 'babel-loader',
          options: {
              //...
          }
      }
      
    • 使用 use 字段则比较自由,可以是一个字符串
      {
          test: /\.js$/,
          use: 'babel-loader'
      }
      
    • 可以是一个数组
      {
          test: /\.css$/,
          use: ['style-loader', 'css-loader']
      }
      
    • 还可以是一个对象,其 options 属性用于配置 loader
      {
          test: /\.js$/,
          use: {
              loader: 'babel-loader',
              options: {
                  presets: ["@babel/preset-env"]
              }
          },
      }
      

mode

mode 用于指定打包构建时的模式,支持两个配置项

  • development
    process.env.NODE_ENV 的值设置为development,启用NamedChunksPlugin、NamedModulesPlugin
  • production
    process.env.NODE_ENV 的值设置为production,启用FlagDependencyUsagePlugin、FlagIncludedChunksPlugin、ModuleConcatenationPlugin、NoEmitOnErrorsPlugin、OccurrenceOrderPlugin、SideEffectsFlagPlugin、UglifyJsPlugin(JS压缩)

mode可以配置在webpack.config.js中,然后直接使用 npx webpack 进行编译。

module.exports = {
    mode: "development",
    // ...
}

cross-env

这是一个运行跨平台设置和使用环境变量的脚本。
在配置文件中,经常会使用 process.env.NODE_ENV 来判断当前是development还是production,但process.env默认并没有NODE_ENV
cross-env可以兼容运行平台去设置环境变量NODE_ENV,比如Windows系统不支持NODE_ENV=development的设置方式。
cross-env能够提供一个设置环境变量的scripts,让你能够以unix方式设置环境变量,然后在Windows上兼容运行。

  • 安装
    npm i cross-env -D
    
  • 配置package.json
        "scripts": {
            "dev": "cross-env NODE_ENV=development webpack",
            "build": "cross-env NODE_ENV=production webpack"
        }
    
  • webpack.config.js中使用process.env.NODE_ENV,如动态配置mode
    const ENV = process.env.NODE_ENV
    module.exports = {
        mode: ENV,
        //...
    }
    

构建HTML页面

编译HTMLwebpack插件:html-webpack-plugin

  • 安装插件
    npm i html-webpack-plugin -D 
    
  • 在根目录下创建 index.html,快捷键 html:5 生成HTML
  • 配置webpack.config.jsplugins字段是一个数组,存放所有的webpack插件
    // 引入插件
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    module.exports = {
        //...
        plugins: [
            new HtmlWebpackPlugin({
                template: './index.html',
                filename: 'index.html', //打包后的文件名
                minify: {
                    minimize: true,  // 是否打包为最小值
                    removeAttributeQuotes: true,  //是否删除属性的引号
                    collapseWhitespace: false,  //去除空格
                    removeComments: true,  // 去除注释
                    minifyCSS: true,  // 压缩HTML内的CSS
                    minifyJS: true,  //压缩HTML内的JS
                    removeEmptyElements: true  //清理内容为空的元素
                },
                hash: true  //引入产出资源时加上哈希,避免缓存
            })
        ]
    }
    
  • 执行 npm run dev 构建,生成dist/index.html,且自动注入了<script>脚本,引入打包后的js文件
    <script src="main.js?ccec26148a41640c2cf0"></script>
    // hash: true 在引入资源后加上哈希值
    

html-webpack-plugin 中的 config 妙用
html-webpack-plugin插件在解析HTML时,支持类似ejs语法。同时会向HTML中注入该插件的对象htmlWebpackPlugin,使用其中配置的各种属性。
例如,我们想自由配置开发版和发布版的页面展示不同的内容;

  • 新增一个自定义的配置文件public/config.js
    module.exports = {
        development: {  // 开发配置项
            template: {
                title: 'Hello development',
                header: false,  // 不要头部和尾部
                footer: false
            }
        },
        production: {  // 发布配置项
            template: {
                title: 'Hi production',
                header: true,  // 只要头部
                footer: false
            }
        }
    }
    
  • webpack.config.js 中配置html-webpack-pluginconfig 属性
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const ENV = process.env.NODE_ENV
    const config = require('./public/config')[ENV]
    module.exports = {
        mode: ENV,
        //...
        plugins: [
            new HtmlWebpackPlugin({
                template: './index.html',
                filename: 'index.html',
                config: config.template
            })
        ]
    }
    
  • index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        // ...
        <% if(htmlWebpackPlugin.options.config.header) { %>
        <link rel="stylesheet" type="text/css" href="//common/css/header.css">
        <% } %>
        <title><%= (htmlWebpackPlugin.options.config.title) %></title>
    </head>
    <body>
        hello back
    </body>
    <% if(htmlWebpackPlugin.options.config.footer) { %>
    <script src="//common/header.min.js" type="text/javascript"></script> 
    <% } %>
    </html>
    
  • 分别使用 npm run devnpm run build 构建,对比dist/index.html的不同

更多html-webpack-plugin配置项

实时展示页面

webpack-dev-server 用于配置本地服务器,可以为webpack打包生成的资源文件提供web服务

  • 为静态文件提供web服务
  • 自动刷新和热替换(HMR)
npm i webpack-dev-server -D

修改package.json

  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server",
    "build": "cross-env NODE_ENV=production webpack"
  },

webpack.config.js 中进行webpack-dev-server的其他配置

module.exports = {
    //...
    devServer: {
        open: true,   // 自动打开首页,默认false
        port: '3000',   // 默认是8080
        hot: true,  // 热加载
        clientLogLevel: "warning",   // 日志等级
        compress: true,    // 是否启用 gzip 压缩
        proxy:  // 代理
        // ...
    }
}

执行npm run dev,现在修改 src/index.js,页面控制台会实时刷新,当前刷新的是整个页面。
更多的配置可以点击查看

devtool

sourcemap是为了解决实际运行代码(打包后)出现问题时,无法定位到开发环境中的源代码;

查看页面控制台上打印的log日志,会发现对应的行号是代码编译后的,而不是当前源码中的行号。
devtool 配置项可以帮助我们将编译后的代码映射回源码中,不同的值会明显影响构建和重新构建的速度。
综合构建速度,在开发模式下通常设置为 cheap-module-eval-source-map;在生产环境下则设置为source-map

// webpack.config.js
module.exports = {
    devtool: 'cheap-module-eval-source-map'
}
  1. sourcemap的5种基本devtool选项
    • eval 每个模块都使用eval()执行,每个模块后会增加sourceURL来关联模块处理前后的对应关系;
      由于会映射到转换后的代码,而不是映射到原始代码(没有从loader中获取sourcemap),所以不能正确显示行号;又因为不需要生成模块的sourcemap,所以打包速度很快。
    • source-map 会为模块生成独立的sourcemap(.map)文件,我们可以根据报错信息和.map文件进行错误分析,定位到源码;
    • inline 不会生成独立的.map文件,sourcemap转换为DataUrl后添加到bundle中;
    • cheap 在打包后同样会为每个模块生成.map文件,但与source-map的区别在于,它生成的.map文件会忽略原始代码中的列信息,也不包含loadersourcemap
    • moudle:包含了loader模块之间的sourcemap,将loader source map简化为每行一个映射;
  2. 基本类型之间可以相互搭配,如eval-source-map、inline-cheap-model-source-map、cheap-module-eval-source-map、hidden-source-map、nosources-source-map
    • 开发环境常用:eval、eval-source-map、cheap-eval-source-map、cheap-module-eval-source-map
    • 生产环境常用:none(省略devtool选项,不生成sourcemap)、source-map、hidden-source-map、nosources-source-map
  3. source-maphidden-source-map 都会打包生成单独的 .map 文件。区别在于,source-map会在打包出的js文件中增加一个引用注释,以便开发工具知道在哪里可以找到它;hidden-source-map 则不会在打包的js中增加引用注释。

但是我们一般不会直接将 .map 文件部署到CDN,因为会直接映射到源码,更希望将 .map 文件传到错误解析系统,然后根据上报的错误信息,直接解析到出错的源码位置。

CSS样式

webpack处理css也必须借助loaderstyle-loader、css-loader
考虑到兼容性问题,还需要 postcss-loader 配合 autoprefixer 插件。
对于Less、Sass,还需要 less-loadersass-loader

// 以 less 为例
npm i style-loader css-loader less-loader postcss-loader autoprefixer less -D
  • style-loader 动态创建 style 标签,将 css 插入到 head 中;
  • css-loader 负责编译CSS,处理 @import 等语句;
  • postcss-loaderautoprefixer,自动生成浏览器兼容性前缀;
  • less-loader 负责处理编译 .less 文件,将其转为 css

配置 webpack.config.js

    test: /\.(le|c)ss$/,
    use: ['style-loader', 'css-loader', {
        loader: 'postcss-loader',
        options: {
            plugins: [require('autoprefixer')({  // 应该配置到 package.json 或 .browserslistrc
                overrideBrowserslist: ['ie >= 8', 'Firefox >= 20', 'Safari >= 5', 
                    'Android >= 4', 'Ios >= 6',
                    'last 4 version'  // 浏览器最新的4个版本
                ]
            })]
        }
    }, 'less-loader']

应该将 overrideBrowserslist 配置到package.json

// package.json
"browserslist": [
  "ie >= 8", "Firefox >= 20", "Safari >= 5",
  "Android >= 4", "Ios >= 6", "last 4 version"
]

// webpack.config.js
    test: /\.(le|c)ss$/,
    use: ['style-loader', 'css-loader', {
        loader: 'postcss-loader',
        options: {
            plugins: [require('autoprefixer')]
        }
    }, 'less-loader']

src/index.js 中引入CSS尝试一下吧:import './base.less';

注意:loader的执行顺序 从右向左。另外,loader中还有一个指定优先级的参数enforce --> 值 pre(优先执行),post (滞后执行)

文件处理

CSS中引入图片、字体等资源文件时,如background-image: url(./a.png),需要使用 url-loaderfile-loader 来处理。
它们功能类似,但url-loader可以限制文件大小,返回DataURL。另外,使用url-loader时也必须安装file-loader,但不要同时配置,会冲突。

  1. 安装与配置

    npm install url-loader file-loader -D
    
    //webpack.config.js
        test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/,
        use: [
            {
                loader: 'url-loader',
                options: {
                    limit: 10240, // 10K 的限制
                    esModule: false 
                }
            }
        ]
    
    • limit 资源小于10K时,则转为base64;超过10K 则将图片拷贝到dist目录。base64可以较少网络请求,但base64数据太大或资源太多,都会导致加载变慢,所以设置limit时要两者兼顾;
    • esModule 设置为false,否则<img src={require('XXX.jpg')} /> 会出现<img src=[Module Object] />
  2. CSS中引入图片资源

    body {
        box-sizing: border-box;
        background-image: url(./images/bg.png);
    }
    
  3. 默认情况下,打包生成的资源名是文件内容的 MD5 哈希值,并会保留其扩展名。

    // 图片默认拷贝到 dist 根目录
    background-image: url(c5c9a52347e5c5899c2bca539cd016f2.png);
    

url-loader options的更多配置

    • [hash]:内容的哈希值,作为打包后的默认文件名,[hash:6]表示取哈希值的前6位作为文件名;
    • [ext]:资源文件的原扩展名;
    • [name]:资源文件的原名称;
    • [path]:资源相对于context的路径,即默认相对于webpack.config.js的路径。
  • 属性
    • name:配置打包后的文件名,默认值为 [hash].[ext]
    • context:配置文件的上下文,默认为webpack.config.js
      [path]配合使用可以控制打包后的文件目录,默认相对于webpack.config.js
      1. 假设 bg.png 存放目录 src/images,src目录与webpack.config.js同级别
      2. 配置文件名
         options: {
            name: '[path][name].[ext]'
         }
      3. 编译打包后,bg.png 输出目录 dist/src/images
         background-image: url(src/images/bg.png);
      4. 配置context,假设项目目录名为webtest
         options: {
             name: '[path][name].[ext]',
             context: '../'  // 向上一级,webpack.config.js上一级目录是项目目录
         }
      5. 编译打包后,bg.png 输出目录 dist/webtest/src/images
         background-image: url(webtest/src/images/bg.png);
      
    • publicPath:文件的 public 发布目录,可以使用该属性配置资源的CDN路径
      options: {
          name: '[name]_[hash:6].[ext]',
          publicPath: 'http://www.xxx.com/img'
      }
      
       background-image: url(http://www.xxx.com/img/bg_c5c9a5.png);
      
    • outputPath:文件的 output 输出目录,默认会拷贝到dist根目录
      options: {
          name: '[name].[ext]',
          outputPath: 'assets/'
      }
      // 匹配的资源文件会拷贝到 dist/assets 目录中
      background-image: url(assets/bg.png);
      

HTML中的本地资源

标签<img /> 也可以引入图片资源,但url-loader是不能直接处理的,需要使用html-loader
html-loader处理HTML中的<img />时,还需要结合url-loader/file-loader 才可以将<img />的路径正确打包,同时记得配置url-loaderesModule: false

<img src="./imgs/a.png" />

npm i html-loader -D

# webpack.config.js
test: /\.html$/,
loader: 'html-loader',
options: {
    attributes: false,
    // attributes: {
    //     list: [
    //         { tag: 'img', attribute: 'src', type: 'src' },
    //         // ...
    //     ]
    // },
}

除了img srchtml-loader还可以处理video src
但是,html-loader会导致html-webpack-plugin解析HTML中的<% ... %>失败。

  • 删除html-loader
  • 修改img src
    <img src="<%= require('./imgs/a.png') %>" />
    
    图片会经过 url-loader 的处理,小于10K(limit: 10240)将被转为base64
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342