小菜前端的webpack学习日记 -- 基础篇

安装

在使用webpack以前咱们要安装webpack,安装命令以下:css

# 建立webpack-demo文件夹
mkdir webpack-demo
# 生成package.json文件,对项目依赖进行管理
yarn init -y
# 安装webpack相关依赖
yarn add webpack webpack-cli -D
复制代码

这里咱们安装webpack-cli的缘由是由于它可让咱们能够在命令行中运行webpack,不然webpack命令将没法运行html

生成打包文件

接下来咱们创建src目录来管理咱们的源代码,并创建main.js文件,写入第一行代码:vue

console.log('Hello webpack!')
复制代码

接下来咱们在项目根目录创建webpack的配置文件webpack.config.js并进行最简单的配置:node

const path = require('path');
module.exports = {
  mode: 'development',
  entry: {
    main: './src/main.js'
  },
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'main.js'
  }
}
复制代码

以后咱们经过package.json文件中的script命令来为webpack添加快捷命令:
react

配置好后经过命令行执行:yarn build,会发现根目录出现了dist目录,里边有咱们的代码main.jswebpack

接下来咱们在根目录新建index.html,经过script标签将打包后的代码引入并在浏览器中打开:
es6

或者能够经过node命令来运行打包文件:
web

到这里咱们成功打包好了咱们的第一个文件,感受仍是有些小激动的。chrome

入口和出口

在配置以前,咱们先简单理解下入口和出口的概念:npm

  • 入口(entry): 整个项目的起始模块,用来做为构建其内部依赖图的开始
  • 出口(output): 告诉webpack打包后的文件所存放的目录

这里是一个打包截图:

了解了基本概念以后,咱们来看一下经常使用的相关配置:

entry: {
  main: './src/main.js' // 入口文件对应的源代码位置,key值为打包生成后的chunkNames
},
output: {
  path: path.resolve(__dirname, './dist'), // 打包生成文件存放的位置
  // 使用每次构建过程当中,惟一的hash生成
  filename: '[name]_[hash].js', // 每一个打包输出文件的名称
  // publicPath: 'https://cdn.example.com/assets/', // 会在引入的资源前加入该路径,例:将资源托管到cnd
}
复制代码

在配置文件中咱们使用了[name]这种符号,这是webpack中的占位符(placeholder),这里介绍下配置中使用到的,若是小伙伴们感兴趣能够本身查阅官方文档:

  • [name]: 模块名称
  • [hash]: 模块标识符的hash(每次打包都会生成对应惟一的hash值)

打包模式

webpack中,咱们能够经过mode选项,来分别为生成环境打包和开发环境打包使用相应的内置优化:

mode: 'development' 
复制代码

目前咱们的配置是开发环境: development,当咱们使用production的时候,webpack会将我node中的全局变量改成: process.env.NODE_ENV的值设置为production,而且也会启用代码压缩等功能,有助于帮咱们减小代码体积,提高用户体验。

而当咱们使用development模式时,webpack会将process.env.NODE_ENV的值设置为development,并取消代码压缩等功能,提高开发体验

使用插件

webpack中,咱们可使用各类各样的插件来自定义webpack构建过程,方便咱们的开发。

这里咱们介绍2个经常使用的plugin

  • HtmlWebpackPlugin: 自动建立一个html文件来帮咱们引入打包文件,这对咱们每次打包都经过hash值来生成不一样的打包文件的状况特别有用
  • CleanWebpackPlugin: 在打包以前删除output.path指定的位置中的文件,保证每次打包都是最新的文件

首先咱们来安装这俩个插件:

yarn add --dev html-webpack-plugin clean-webpack-plugin
复制代码

接下来咱们再webpack中使用这2个插件:

// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

plugins: [
  new HtmlWebpackPlugin({
    filename: 'index.html', // 生成html文件的文件名
    template: './index.html' // 使用的html模板
  }),
  new CleanWebpackPlugin()
]
复制代码

这里咱们经过为html-webpack-plugin来指定根目录下的index.html为模版,生成打包后的index.html。新生成的index.html文件与模板文件不一样之处在于会自动引入打包生成的文件(包括js文件、css文件、以及以后配置的dll文件),这样即便咱们使用了[hash]等占位符,插件也会自动帮咱们动态引入资源,而不用咱们每次手工配置。

使用HtmlWebpackPlugin指定template的缘由是由于咱们能够在模板文件中本身写一些咱们本身的代码,好比引入一些css或者执行一段js脚本亦或者咱们能够在模板文件中指定项目的根元素:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <!-- 在模板中指定根元素,方便开发时为其中插入元素 -->
  <div id="root"></div>
</body>

</html>
复制代码

经过使用插件咱们目前解决了以下问题:

  1. 不用再手动引入打包文件
  2. 不用在从新打包的时候手动去删除旧的打包文件

使用loader

下面是对webpack官网中loader介绍的一步分摘抄:

loader用于对模块的源代码进行转换
特性(这里只列出了部分):

  • loader支持链式传递,链中每一个loader会将转换应用在已处理过的资源上。一组链式的loader将按照相反的顺序执行。链中的第一个loader将其结果(也就是转换后的资源)传递给下一个loader,依次类推
  • loader能够经过options对象配置
  • 插件能够为loader带来更多特性

我我的以为其实loader就是让webpack能够识别各类资源,而后将资源加工处理成浏览器能够识别的、兼容性更好的、性能更好的代码。

接下来咱们学习如何经过loader来让webpack处理各类资源。

项目中使用css

要想使用css文件,咱们首先须要安装style-loadercss-loader`:

yarn add style-loader css-loader -D
复制代码

webpack.config.js进行配置:

module: {
  rules: [
    {
      test: /.css$/,
      use: ['style-loader', 'css-loader']
    }
  ]
}
复制代码

可是平常的项目中,有不少css属性须要添加浏览器供应商前缀来确保兼容性, 咱们可使用postcss-loader结合autoprefixer来实现:

yarn add postcss-loader autoprefixer -D
复制代码

咱们须要为css文件添加postcss-loader,以后在根目录新建postcss.config.js配置autoprefixer:

// webpack.config.js
rules: [
  {
    test: /\.css$/,
    use: [
      'style-loader',
      'css-loader',
      'postcss-loader'
    ]
  }
]

// postcss.config.js
module.exports = {
  plugins: [
    // autoprefixer: parse CSS and add vendor prefixes to CSS rule using values from can I use
    // 解析CSS并使用Can I use 中的值 将供应商前缀添加到css规则中
    require('autoprefixer')
  ]
};
复制代码

日常咱们也会用到css预处理器来方便开发,这里咱们以sass为例:

yarn add sass-loader node-sass -D
复制代码

webpack中添加以下配置:

{
  test: /\.scss$/,
  use: [
    'style-loader',
    'css-loader',
    'postcss-loader',
    'sass-loader'
  ]
}
复制代码

这里要特别注意loader的顺序问题(反向:从下到上,从右到左):

  • 首先经过sass-loaderscss文件中的语法解析为css语法
  • 以后经过postcss-loader为对应的css属性添加浏览器供应商前缀
  • 而后使用css-loader根据import语法将全部的css文件整合到一块儿
  • 最后将css-loader整合的css文件经过style标签插入到html中,实现样式的更改

若是顺序书写错误,会致使程序没法正常运行

最终效果以下:

学习了上面的知识之后,咱们再了解几个css-loader的经常使用配置:

{
  test: /\.scss$/,
  use: [
    'style-loader',
    {
      loader: 'css-loader',
      options: {
        // 开启css模块化
        modules: true,
        // 在css-loader前应用的loader的数量:确保在使用import语法前先通过sass-loader和postcss-loader的处理
        importLoaders: 2
      }
    },
    'postcss-loader',
    'sass-loader'
  ]
}
复制代码

项目中使用图片和字体图标

要想在项目中使用图片和字体图标,咱们须要使用到file-loader:

yarn add file-loader -D
复制代码

须要在webpack.config.js中添加以下配置:

{
  test: /\.(png|svg|jpg|gif)$/,
  use: ['file-loader']
}
复制代码

配置成功后webpack会将咱们引入的资源转换为路径字符串,webpack会经过字符串帮咱们找到文件的位置。

接下来咱们尝试一下file-loader的几个配置项:

{
  test: /\.(png|svg|jpg|jpeg|gif)$/,
  use: [
    {
      loader: 'file-loader',
      options: {
        // placeholders:
        // [ext]: 资源扩展名
        // [name]: 资源的基本名称
        // [hash]: 内容hash值
        // [path]: 资源相对于context的路径
        // 默认值: [hash].[ext]
        name: '[name]_[hash:8].[ext]',
        // 打包文件存放到出口目录下的images文件中
        outputPath: 'images/'
      }
    }
  ]
}
复制代码

打包文件以下:

这里也可使用url-loader来处理静态资源,url-loader的工做方式和file-loader相同,可是它会在文件小于限制大小(单位byte)的时候,返回base64字符串,当大于限制大小时会返回和file-loader同样的地址字符串。

这在处理一些小文件的时候不用再进行资源请求,能够提升性能

{
  test: /\.(png|svg|jpg|jpeg|gif)$/,
  use: [
    {
      loader: 'url-loader',
      options: {
        // placeholders:
        // [ext]: 资源扩展名,默认file.extname
        // [name]: 资源的基本名称,默认file.basename
        // [hash]: 内容hash值,默认md5
        // [path]: 资源相对于context的路径,默认值file.dirname
        // 默认值: [hash].[ext]
        name: '[name]_[hash:8].[ext]',
        // 打包文件存放到出口目录下的images文件中
        outputPath: 'images/',
        limit: 8192, // 单位byte,文件小于8kb时返回base64文件,大于这个限制会返回地址
      }
    }
  ]
}
复制代码

咱们能够看一下打包区别:

source map配置

当咱们使用webpack进行代码打包之后,若是代码出现错误,会很难追踪到错误和警告在源代码中的原始位置。为了更容易地追踪错误和警告,JavaScript提供了SourceMap功能,能够将编译后的代码映射回原始源代码。

webpack中配置以下:

// 此选项控制是否生成,以及如何生成source map
devtool: 'none',
复制代码

可选项以下:

这里简单介绍下各配置项中单词的一些含义,方便理解:

  • cheap: 不会生成列映射,只会映射行数
  • eval: 每一个模块都使用eval执行,而且都有//@sourceURL
  • module:会处理来自loadersource map
  • inline: source map转换为DataUrl后添加到bundle

平常的开发中咱们会进行以下配置:

  • 开发环境: cheap-module-eval-source-map
  • 生产环境: cheap-module-source-map(生产环境使用source map,方便定位错误)

区分不一样环境的配置文件

在开发环境和生产环境中,咱们要用到的webpack配置是不一样的,这里咱们在根目录下分别新建webpack.dev.jswebpack.prod.js来分别存放开发环境和生产环境所须要的配置,经过原有的webpack.config.js来存放公共的配置文件。

接下来咱们使用webpack-merge来进行配置文件的合并工做:

yarn add webpack-merge -D
复制代码

为了方便统一管理,咱们根目录下新建config文件夹,而后将配置文件放到里边,如今的配置文件是这样的:

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const absPath = (dir) => path.resolve(__dirname, dir)
module.exports = {
  entry: {
    main: absPath('../src/main.js') // 入口文件对应的源代码位置,key值为打包生成后的chunkNames
  },
  output: {
    path: absPath('../dist'), // 打包生成文件存放的位置
    // 使用每次构建过程当中,惟一的hash生成
    filename: '[name]_[hash].js', // 每一个打包输出文件的名称
    // publicPath: 'https://cdn.example.com/assets/', // 会在引入的资源前加入该路径,例:将资源托管到cnd
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader',
        ]
      },
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              // 开启css模块化
              modules: true,
              // 在css-loader前应用的loader的数量:确保在使用import语法前先通过sass-loader和postcss-loader的处理
              importLoaders: 2
            }
          },
          'postcss-loader',
          'sass-loader'
        ]
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              // placeholders:
              // [ext]: 资源扩展名,默认file.extname
              // [name]: 资源的基本名称,默认file.basename
              // [hash]: 内容hash值,默认md5
              // [path]: 资源相对于context的路径,默认值file.dirname
              // 默认值: [hash].[ext]
              name: '[name]_[hash:8].[ext]',
              // 打包文件存放到出口目录下的images文件中
              outputPath: 'images/',
              limit: 8192, // 单位byte,文件小于8kb时返回base64文件,大于这个限制会返回地址
            }
          }
        ]
      }
    ]
  },
  plugins: [
    // 自动引入打包后的文件到html中:
    // 对于每次打包都会从新经过hash值来生成文件名的状况特别适用
    // 也能够经过template来生成一个咱们本身定义的html模板,而后帮咱们把打包后生成的文件引入
    new HtmlWebpackPlugin({
      filename: 'index.html', // 生成html文件的文件名
      template: absPath('../index.html') // 使用的html模板
    })
  ]
};
// webpack.dev.js
const merge = require('webpack-merge');
const baseConfig = require('./webpack.config')
module.exports = merge(baseConfig, {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map'
})
// webpack.prod.js
const merge = require('webpack-merge');
const baseConfig = require('./webpack.config')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = merge(baseConfig, {
  mode: 'production',
  devtool: 'cheap-module-source-map',
  plugins: [
    new CleanWebpackPlugin()
  ]
})
复制代码

而后咱们在package.json中分别为开发环境和生产环境添加打包快捷命令:

"scripts": {
  "build": "webpack --config ./config/webpack.prod.js",
  "start": "webpack --config ./config/webpack.dev.js"
},
复制代码

如今目录结构是这样的:

接下来咱们就能够直接使用yarn buildyarn start命令来分别为开发环境和生产环境进行打包了

webpackDevServer方便开发和调试

如今咱们在每次修改代码后,都须要再次从新打包文件,并在浏览器中打开生成的html文件。并且因为是本地打开html文件,诸如请求代理、局域网内预览等功能便没法使用。

webpack-dev-server为咱们提供了一个简单的web服务器,可以实时从新加载(live reloading)修改后的代码,而且也提供了如proxy等功能来支持咱们跨域请求接口,极大的方便了开发。

yarn add webpack-dev-server -D
复制代码

以后咱们在开发环境的webpack配置中添加以下配置:

devServer: {
  // 告诉服务器从哪里提供内容。只有在你想要提供静态文件时才须要
  contentBase: absPath('../dist'),
  // 是否在启动服务时自动打开浏览器
  open: true,
  // 端口在没有设置的时候默认为8080
  port: 3000
}
复制代码

固然,这样更改以后咱们要经过webpack-dev-server来执行开发时的配置文件:

// "start": "webpack --config ./config/webpack.dev.js"
"start": "webpack-dev-server --config ./config/webpack.dev.js"
复制代码

执行yarn start:

实现代码的热模块更新

当咱们在开发过程当中修改一些代码的时候,咱们可能只是想让咱们当前修改的内容生效,而不是形成整个页面的刷新,致使咱们每次修改都要从新以前的步骤来验证咱们的代码。

webpack中咱们经过以下配置来实现模块热替换的功能(hot module replacement),固然这个功能只在开发环境开启:

// webpack.dev.js
devServer: {
  // 告诉服务器从哪里提供内容。只有在你想要提供静态文件时才须要
  contentBase: absPath('../dist'),
  // 是否在启动服务时自动打开浏览器
  open: true,
  // 端口在没有设置的时候默认为8080
  port: 3000,
  // 启用webpack的模块热替换特性
  hot: true
},
plugins: [
  new webpack.NamedModulesPlugin(),
  new webpack.HotModuleReplacementPlugin()
]
复制代码

和以前配置的区别有2个:

  • devServer添加配置项: hot: true
  • 新增2个webpack的插件: webpack.NameModulesPluginwebpack.HotModuleReplacementPlugin

而后这样只能实现css文件的热更新,对于js的热更新,咱们还须要添加一下代码:

// accept接收2个参数: 1. dependencies: 一个字符串或字符串数组 2. callback: 模块更新后触发的函数
if (module.hot) {
  module.hot.accept('./utils/printSomething', () => {
    console.log('update module');
  })
}
复制代码

页面中的效果以下:

你们可能会发现咱们在写css和相似于vuereact框架代码的时候,并无本身手写module.hot.accept方法,这是框架和cssloader已经帮咱们进行了自动处理,咱们只须要关注代码的书写便可。

使用babel转义es6语法

在平常工做中,咱们会使用不少es6里的新语法,这些语法在咱们目前使用的chrome新版浏览其中通常均可以很好的支持,可是在一些国产浏览器或者低版本浏览器中可能会出现兼容性问题。

这里咱们须要经过babel-loader来进行语法的转义:

yarn add babel-loader @babel/core @babel/preset-env -D
复制代码

webpack中进行配置:

// webpack.config.js 中添加一个loader配置项
{
  test: /\.js$/,
  exclude: /node_modules/,
  use: {
    loader: 'babel-loader',
    options: {
      cacheDirectory: true
    }
  }
},
复制代码

项目根目录创建.babelrc文件,并加入以下代码:

{
  "presets": [
    "@babel/preset-env"
  ]
}
复制代码

文档地址

这样配置以后,项目中的Promisemap等语法依旧不会进行转换,这里咱们使用@babel/polyfill来转换这些语法:

yarn add @babel/polyfill -D
复制代码

.babelrc进行以下配置:

{
  "presets": [
    [
      "@babel/env",
      {
        // 为低版本引入babel/polyfill
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1",
        },
        // 使用useBuiltIns要指定corejs
        "corejs": "2",
        // 只对项目中用到的`esnext`语法进行polyfill处理
        "useBuiltIns": "usage",
      }
    ]
  ]
}
复制代码

这样配置以后就能够经过babel来转换esnext的一些语法,而且能够兼容低版本和国产浏览器。因为使用到了useBuiltIns:usage,只会对咱们使用到的新语法进行转换,减小了polyfill的体积

配置react开发环境

在学习完以上内容之后,咱们须要搭建一个支持react框架语法的webpack配置。

首先咱们安装相应的依赖:

yarn add react react-dom
yarn add @babel/preset-react @babel/plugin-proposal-class-properties -D
复制代码

.babelrc中配置以下:

{
  "presets": [
    [
      "@babel/env",
      {
        // 为低版本引入babel/polyfill
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1",
        },
        // 使用useBuiltIns要指定corejs
        "corejs": "2",
        // 只对项目中用到的`esnext`语法进行polyfill处理
        "useBuiltIns": "usage",
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": [
    "@babel/plugin-proposal-class-properties"
  ]
}
复制代码

以后咱们在入口文件main.js中写入以下代码:

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  state = {
    number: 10
  }
  render() {
    return (
      <div> hello Webapck React <h2>{this.state.number}</h2> </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root')) 复制代码

执行yarn start命令:

到这里为止,咱们已经能够搭建一个简单的react开发环境,以后咱们能够继续学习一些webpack的高级概念。

相关文章
相关标签/搜索