星星博客 »  > 

使用webpack来构建项目

 
1、在webpack的官网可以看到,webpack是一个 文件打包工具,它将复杂的的文件依赖打包成独立的资源文件。换句话说, 在webpack里一切文件都是模块,通过loader加载文件,通过plugin注入钩子,最后输出由多个模块组合的文件。那么loader是什么呢?loader用来读取各类资源,比如css、js等。模块loader可以链式调用,链汇总的每个loader都将对资源进行转换,然后将结果传递给下一个loader。
 
也就是说webpack使用loader来管理各类的资源。
 
2、使用webpack来管理资源 webpack.config.js
 
const path = require('path');


module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
};

 

 
需要注意的是需要保证 loader 的先后顺序: 'style-loader' 在前,而 'css-loader' 在后。如果不遵守此约定,webpack 可能会抛出错误
 
根据上面的配置中,webpack执行的时候就会把对应的文件当做是一个模块进行处理,你可以import对应的资源进行使用了。
 
 
2、 在安装一个 package,而此 package 要打包到生产环境 bundle 中时,你应该使用 npm install --save。如果你在安装一个用于开发环境的 package 时(例如,linter, 测试库等),你应该使用 npm install --save-dev
 
3、我们知道的是,webpack会生成bundle.js文件,那么,当某一个源文件中出现了一个错误,我们在开发的时候就无法定位到具体是哪一个源文件出错了。这个时候我们在开发环境下面使用 source map来定位问题。配置如下
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
    print: './src/print.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
 
  module: {
    
  },
};

 

 
4、我们在使用vue搭建项目的时候,是不是每次修改了一个文件,vue就会自动帮我们重新编译构建并刷新浏览器来着?这是因为vue内置了一个webpack的配置。那如果我们不是使用vue来开发项目又想着可以自动编译和刷新呢?这里我们就可以使用webpack-dev-server来进行配置和搭建实现这个需求。
 
首先我们安装这个插件
npm install --save-dev webpack-dev-server

 

 
然后在配置文件中加入下面的代码
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
    print: './src/print.js',
  },
  plugins: [
     new HtmlWebpackPlugin({
       title: 'Development',
     }),
   ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  devServer: {
    contentBase: './dist',
    hot:true
  },
  module: {
    
  },
};

在package.json中加入下面的srcipt
{
  "name": "wepack-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve --open"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "css-loader": "^5.2.4",
    "html-webpack-plugin": "^5.3.1",
    "style-loader": "^2.0.0",
    "webpack": "^5.4.0",
    "webpack-cli": "^4.2.0",
    "webpack-dev-server": "^3.11.2"
  },
  "dependencies": {
    "loadash": "^1.0.0",
    "lodash": "^4.17.21"
  }
}

 

 
 
 
 
执行npm run start,可以看到自动编译和打开了浏览器,这个时候,修改任意文件都会重新刷新文件
 
这是因为配置中告诉了webpack-dev-server这个插件,将dist目录下面的文件可以通过localhost:8080这个地址可以访问到,webpack-dev-server在编译之后不会写入到任何输出文件。而是将bundle文件保留在内存里面,然后将它们当做是可以被访问的文件。
 
 
5、从上面的例子中,我们可以知道,webpack是可以将你的js文件全部打包到一个bundle文件里面,可是有的情况下面,我们不想要将所有的文件一次性引入进来,有些插件在需要的时候再引入。这个时候我们想到的是不是就是将这个bundle文件分离到不同的bundle中,然后再按需加载进来。这个时候我们就需要用到webpack的代码分离的特性了。
 
在entry里写入多个接口,然后使用 SplitChunksPlugin插件来防止重复加载共同的模块
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
    print: './src/print.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
  plugins: [
    new HtmlWebpackPlugin({//用来管理新的构建的html文件
      title: '管理输出',
    }),
  ],

  devServer: {
    contentBase: './dist',
  },
};

 

然后执行npm run build
可以看到loadash文件只构建了一次
 
 
6、浏览器访问网站的资源的时候,使用缓存技术来加快加载资源的速度。但这又会出现一个问题,就好像我们在项目中修改了很多东西,但是我们文件名没有发生改变。这个时候,浏览器就会以为这个文件没有发生改变,使用缓存的版本。所以我们可以使用webpack的substitution方式来输出文件的名称。这个方式会根据资源的内容创建出唯一的hash,然后再文件后面加上这部分hash。而且这个方式超级简单,我们只需要在webpack的配置文件中修改下面代码:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
    print: './src/print.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
  plugins: [
    new HtmlWebpackPlugin({//用来管理新的构建的html文件
      title: '管理输出',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
  devServer: {
    contentBase: './dist',
  },
};

 

 
现在我们不改变文件内容,再build一下看看
 
可以看到,此时是使用了cache,文件是没有发生变化的
 
此外,我们希望一些引用外部插件的bundle在构建的时候不发生变化,这样每次项目发生变化的时候,重新构建就不要再去构建这些不经常改变的外部插件了。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
    print: './src/print.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
  plugins: [
    new HtmlWebpackPlugin({//用来管理新的构建的html文件
      title: '管理输出',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
  devServer: {
    contentBase: './dist',
  },
};

 

 
这个时候我们可以看到,构建出来的vendor和runtime模块独立出来了,其他自己写的代码模块体积就变小了,这样的资源在二次浏览的时候,就大大的提升了加载速度!牛逼格拉斯!~
 
 
 
6、假设我们自己写了一个插件,想要发布到npm上面去,我们怎么样使用webpack来打包构建这一个插件呢?
其他部分和上面都一样,此外还要申明一下引入方式:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: {
    index: './src/index.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,//每次新构建之前都会清理一下输出的文件夹
    library: {
      name: 'webpackNumbers',
      type: 'umd',
    },
  },
};

 

 
 
 
7、webpack 4中开始引入了tree-shaking,它主要的作用就是消除项目中无用的代码。这样依赖,项目的体积就会大大提升了。也许你会问,项目中还会有无用的代码吗?那不是有用才会写!
那不是这样的,在项目开发的过程中,需求是会不断的改变,一个页面里面引入的组件可能在这一个阶段有用,但是在下一个阶段里,这个功能有可能就会被废弃了。维护的时间越长,废弃的代码可能会越多。
这样,tree-shaking就大大压缩了打包后的体积了。也许你又会问,这个东西内部是怎么识别代码有没有用的,tree-shaking依赖于模块的特性,也就是import和export。。Webpack 跟踪整个应用程序的 import/export 语句,因此,如果它看到导入的东西最终没有被使用,它会认为那是“死代码”,并会对其进行 tree-shaking
 
那么什么样的代码会认为是未被使用的代码呢?我们先来看一下例子
import _ form  'lodash';//这样的导入,webpack会认为这整个lodash的库你都有使用到,就不会动这整个库的代码,所以不建议引入整个库。

import {join} from 'lodash';//这样具名引入的时候,如果后续的代码没有使用这个引入的方法,就会被当做是废弃的代码,然后tree-shaking

 

 
接下来我们在webpack的配置中开启tree-shaking
需要注意的是,webpack官网中指定,必须要在生产模式下面才可以开启tree-shaking,这也可以理解,因为只有在压缩打包代码的时候才会tree-shaking,所以只有在生产模式下才会,所以最后打包的时候记得要将mode 改成production
const path = require('path');


module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
 mode: 'development',
 optimization: {
   usedExports: true,//Webpack 将识别出它认为没有被使用的代码,并在最初的打包步骤中给它做标记
 },
};

 

 
 
接下来我们先理解一个概念:sideEffects,副作用
在上面的配置里面,我们已经让webpack去筛选那些没有被使用的代码,但有些代码引入没有被使用不代表它是一个废弃的代码。比如我们引入样式,使用全局样式表,或者是一个引入全局配置的配置文件。 这样被引入没有被使用但依然起起作用的文件, webpack认为这样的文件有“ sideEffects ”,这样的文件不应该被tree-shaking。为了避免整个项目有副作用的文件,webpack默认将所有的文件视为有副作用,这样就是整个项目都不能tree-shaking,所以我们先配置一下sideEffects,告诉webpack你可以进行tree-shaking。
在package.json中配置
// 所有文件都有副作用,全都不可 tree-shaking
 { 
    "sideEffects": true
 } 

// 没有文件有副作用,全都可以 tree-shaking 
{ 
    "sideEffects": false 
} 


// 只有这些文件有副作用,所有其他文件都可以 tree-shaking,但会保留这些文件 

{ 
    "sideEffects": [ 
        "./src/file1.js", 
        "./src/file2.js" 
    ] 
}

 

 
此外,还可以在loader中配置sideEffects
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    publicPath:'/',//指定资源的目录
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
  plugins: [
    new HtmlWebpackPlugin({//用来管理新的构建的html文件
      title: '管理输出',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
        sideEffects:true,
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
  devServer: {
    contentBase: './dist',
    hot: true,
  },
};


 

然后执行npm run build的时候,废弃代码就被删除掉了。下面我们来看一下前后代码的对比
 
这是我引入lodash之后没有使用的然后打包的代码(配置之前)
 
可以很清晰的看到最后打包是有吧lodash引进来的,接下来我们按照上面的配置然后打包
记住!这一步很重要,将mode改成production 删除optimization
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'production',//环境:分别为developement\production\none;对应着开发环境、生产环境以及无差别
  devtool: 'inline-source-map',
  entry: {//entry可以有多个入口文件
    index: './src/index.js',
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    publicPath:'/',//指定资源的目录
    clean: true,//每次新构建之前都会清理一下输出的文件夹
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
  plugins: [
    new HtmlWebpackPlugin({//用来管理新的构建的html文件
      title: '管理输出',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
        sideEffects:true,
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
  devServer: {
    contentBase: './dist',
    hot: true,
  },
};

 

 
执行npm run build看下
可以看到此时那些无用的代码就被删掉啦!
 
好了,配置完了我们来总结一下
  • 需要使用模块的语法(import、export)
  • 没有被babel转换成commonJs(Webpack 不支持使用 commonjs 模块来完成 tree-shaking。)
  • package.json中配置sideEffects
  • mode设置为producrion再打包

 

相关文章