Webpack

Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。

webpack中一切皆模块,一个模块对应一个文件,webpack会从配置的entry开始递归找出所有依赖的模块。

Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。

如何使用

文件夹的根目录下新建一个名为webpack.config.js的文件,在其中写入配置代码:

const path = require("path");
const HtmlWebPackPlugin = require("html-webpack-plugin");
const MinCssExtractPlugin = require("mini-css-extract-plugin"); //抽离css插件

module.exports = {
    mode: "development",    // 打包模式
    entry: "./src/index.js",  // 入口文件指定
    output: {
        //出口文件配置,出口打包路径配置
        filename: "[name][hash:8].js", // 打包的文件名称 filename: "build[hash:8] 添加哈希值
        path: path.resolve(__dirname, "build") // resolve绝对路径引入
    },
    devServer: { // 开发服务器配置
        contentBase: "./build", // 指向打包目录
        port: 3000, // 服务端口号
        progress: true, // 打包进度
        open: true, // 是否打开浏览器
        compress: false // 是否压缩
    },
    module: {
        //添加模块模块是对象
        rules: [
            //规则 css-loader主要解析我们样式中@import语法
            {
                test: /\.css$/,
                use: [
                    MinCssExtractPlugin.loader, //创建link标签放入到main.css里
                    "css-loader"
                ] //执行顺序是重右向左执行 - >重下到上
            },
            {
                test: /\.less$/,
                use: [
                    MinCssExtractPlugin.loader, //创建link标签放入到main.css里
                    "css-loader",
                    "less-loader"
                ] //执行顺序是重右向左执行 - >重下到上
            },
        ]
    },
    // 插件
    plugins: [
        //数组形式 存放所有的webpack插件
        new HtmlWebPackPlugin({
            filename: "index.html", //生成打包文件名
            template: "./src/index.html", //模板路径
            minify: {
                removeAttributeQuotes: true, //删除 html文件双引号
                collapseWhitespace: true //折叠控行
            },
            hash: true //添加哈希值
        }),
        new MinCssExtractPlugin({
            filename: "mian.css"
        })
    ],
}

配置项

path模块

路径希望是相对于配置文件的路径的话,需要使用path模块:

var path = require('path')

module.exports = {
    entry: path.resolve(__dirname, './app.js'),
    output: {
        path: path.resolve(__dirname, './output'),
        filename: 'output-file.js'
    }
}

mode

 

会根据不同的环境执行不同的代码。

if(process.env.NODE_ENV === 'development'){
    //开发环境 do something
}else{
    //生产环境 do something
}

context

context 是webpack entry的上下文,是入口文件所处的目录,如果我们不设置context的值,context默认的值就是项目的根目录。

entry

配置入口文件,entry接受三种形式的值:字符串,数组和对象。

对象形式:

entry: {
    <key>: <value>
    ...
}

对象形式是最完整的entry配置,其他形式只是它的简化形式而已。对象中的每一对属性,都代表着一个入口文件。因此多页面配置时,肯定是要用这种形式的entry配置。

key是简单的字符串或路径字符串,对应着output.filename配置中的[name]变量:

entry: {
    'path/of/entry': './deep-app.js',
    'app': './app.js'
},
output: {
    path: './output',
    filename: '[name].js'
}

上边的配置打包后生成:

 value为有效的路径字符串或npm模块字符串,比如文件路径:'./app.js';比如安装的npm模块:'lodash'

字符串形式:

entry: './app.js'

等价于:

entry: {
    main: './app.js'
}

数组形式:

entry: ['./app.js', 'lodash']

等价于:

entry: {
    main: ['./app.js', 'lodash']   // 把多个文件整个到一个文件里
}

output

单一入口配置

output: {
    filename: 'bundle.js',   // 输出的文件名
    path: '/home/proj/public/assets'   // 储存所有webpack打包后的文件的本地存放目录(即项目打包文件的位置)
}

多入口时的配置

{
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/dist'
  }
}
publicPath:webpack中的publicPath是我们打算放到web服务器下的目录,如果我们要放到网站的根目录下,那么就无需设置。如果要放到站点的其它路径,就可以通过设置publicPath来实现。
这样当运行的时候,请求的其它js, css等资源,就会添加上这个路径:
output: {
    publicPath: '/front/'
}

module

module配置如何处理模块

里面的rules数组配置模块的读取和解析规则, 通常用来配置loader,  数组里每一项都描述了如何去处理相应的文件。

配置一项rules大致通过以下方式:

1. 条件匹配: 通过test、include、exclude三个配置来匹配Loader要应用的规则文件,形式为一个字符串或正则,或包含字符串或正则的数组;
2. 应用规则: 对选中后的文件通过use配置项来应用loader,可以应用一个loader或者按照从后往前的顺序应用一组loader,同时还可以分别给loader传入参数;
3. 重置顺序: 一组loader的执行顺序默认是从有道左执行,通过exforce选项可以让其中一个loader的执行顺序放到最前或者是最后;

module: {
    rules: [
        {
            test: /\.js$/, 
            use: ['babel-loader?cacheDirectory'],
            include: path.resolve(__dirname, 'src')
        },
        {
            test: /\.scss$/,
            use: ['style-loader', 'css-loader', 'sass-loader'],
            exclude: path.resolve(__dirname, 'node_modules')
        }
    ]
}

在loader需要传入很多参数去配置它的时候,我们还可以在use中通过一个object来描述,如:

use: [
    {
        loader: 'babel-loader',
        options: {
            cacheDirectory: true
        },
         // enforce:'post' 的含义是把该 Loader 的执行顺序放到最后;enforce 的值还可以是 pre,代表把 Loader 的执行顺序放到最前面
        enforce:'post'
    }
]

noParse可以让webpack对部分没有采用模块化的文件不进行递归解析和处理,这样做的好处是能提高构建性能。原因是一些库例如jq这些,庞大又没有采用模块化标准,让webpack去解析是没有意义的。

module:{
        // 接收参数  正则表达式 或函数
        noParse: /jquery|lodash/,  
        noParse:function(contentPath){   // contentPath是一个复合路径,包含loader的路径以及资源路径
            return /jquery|lodash/.test(contentPath);
        }
}

parse属性可以更细粒度的配置哪些模块语法要解析,哪些不解析。

module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        parser: {
            amd: false, // 禁用 AMD
            commonjs: false, // 禁用 CommonJS
            system: false, // 禁用 SystemJS
            harmony: false, // 禁用 ES6 import/export
            requireInclude: false, // 禁用 require.include
            requireEnsure: false, // 禁用 require.ensure
            requireContext: false, // 禁用 require.context
            browserify: false, // 禁用 browserify
            requireJs: false, // 禁用 requirejs
        }
      },
    ]
  }

plugins

我们在项目中配置相应的plugins就是希望在打包过程中的一些时刻,一些场景下帮助我们做一些事情。

HtmlWebpackPlugin:这个插件可以在打包后生成一个html文件,而这个文件可以配置生成我们想要的样子:

plugins: [
        new htmlWebpackPlugin({
            title: "this is title", //用于生成的HTML文档的标题。
            filename: "index.html", // 生成的模板文件的名字 默认index.html
            template: "index.html", //模板来源文件
            inject: false, //注入位置'head','body',true,false
            favicon: "", //指定页面图标
            minify: {
                caseSensitive: false, ////是否大小写敏感
                collapseBooleanAttributes: true, //是否简写boolean格式的属性如:disabled="disabled" 简写为disabled 
                collapseWhitespace: true //是否去除空格
            },
            hash: true, //是否生成hash添加在引入文件地址的末尾,类似于我们常用的时间戳,这个可以避免缓存带来的麻烦
            cache: true, //是否需要缓存,如果填写true,则文件只有在改变时才会重新生成
            showErrors: true, //是否将错误信息写在页面里,默认true,出现错误信息则会包裹在一个pre标签内添加到页面上
            chunks: ['a', 'b'], //引入的a,b模块,这里指定的是entry中设置多个js时,在这里指定引入的js,如果不设置则默认全部引入,数组形式传入
            chunksSortMode: "auto", //引入模块的排序方式
            excludeChunks: ['a', 'b'], //排除的模块,引入的除a,b模块以外的模块,与chunks相反
            xhtml: false //生成的模板文档中标签是否自动关闭,针对xhtml的语法,会要求标签都关闭,默认false
        })
]

clean-webpack-plugin:在编译之前清理指定目录的指定内容。

plugins: [
    new CleanWebpackPlugin(
    // 清理目录
    [
      'dist',
      'build'
    ],
    // 清理参数
    {
      exclude:  ['shared.js'], // 跳过文件
    }
    )
  ]

webpack.DefinePlugin:定义全局常量

new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: JSON.stringify(process.env.NODE_ENV)
  }
})

resolve

Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。

alias:resolve.alias是给路径设置别名,作用是用别名代替前面的路径。例如使用以下配置:

// 当你通过  import Button from 'components/button 导入时,实际上被 alias 等价替换成了  import Button from './src/components/button' 。
resolve:{
  alias:{
    components: './src/components/'
  }
}
// react$  只会命中以  react  结尾的导入语句,即只会把  import 'react'  关键字替换成  import '/path/to/react.min.js' 
resolve:{
  alias:{
    'react$': '/path/to/react.min.js'
  }
}

mainFields

有一些第三方模块会针对不同环境提供几分代码。 例如分别提供采用 ES5 和 ES6 的2份代码,这2份代码的位置写在package.json文件里,如下:

{
  "jsnext:main": "es/index.js",// 采用 ES6 语法的代码入口文件
  "main": "lib/index.js" // 采用 ES5 语法的代码入口文件
}

Webpack 会根据mainFields的配置去决定优先采用哪部分代码。假如你想优先采用 ES6 的那份代码,可以这样配置:

mainFields: ['jsnext:main', 'browser', 'main']

extensions

在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试访问文件是否存在。extensions用于配置在尝试寻找过程中用到的后缀列表。

也就是说如下配置表示当遇到  require('./data')  这样的导入语句时,Webpack 会先去寻找  ./data.js  文件,如果该文件不存在就去寻找  ./data.json  文件, 如果还是找不到就报错。

extensions: ['.js', '.json']

modules

resolve.modules是用来设置模块搜索的目录,设定目录以后,import模块路径,就可以从一个子目录开始写,这样就可以缩短模块引入路径。

例如:

resolve:{
    modules: ['./src/components']
}

则引入src下的components下的utils模块就可以:

import 'utils'

descriptionFiles

用于描述的 JSON 文件。默认为:

descriptionFiles: ["package.json"]

enforceExtension

如果配置为  true  所有导入语句都必须要带文件后缀, 例如开启enforceExtension前  import './foo'  能正常工作,开启后就必须写成  import './foo.js' 。

enforceModuleExtension

enforceModuleExtension  和  enforceExtension  作用类似,但  enforceModuleExtension  只对  node_modules  下的模块生效。  enforceModuleExtension  通常搭配  enforceExtension  使用,在  enforceExtension:true  时,因为安装的第三方模块中大多数导入语句没带文件后缀, 所以这时通过配置  enforceModuleExtension:false  来兼容第三方模块。

externals(分包)

比如我们在index.html用CDN的方式引入jquery,webpack编译打包时不处理它,却可以引用到它。

<script src="http://code.jquery.com/jquery-1.12.0.min.js"></script>

使用

import $ from 'jquery'

$('#app').css({
    width:'300px',
    height:'300px',
    backgroundColor:'red'
})

配置

externals:{
        //key:包名   value:全局中的jQuery导出的接口
        jquery:'jQuery'
}

optimization.splitChunks(分包)

 

module.exports = {
    //...
    optimization: {
    splitChunks: {
      chunks: 'async',//默认只作用于异步模块,为`all`时对所有模块生效,`initial`对同步模块有效
      minSize: 30000,//合并前模块文件的体积
      minChunks: 1,//最少被引用次数
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',//自动命名连接符
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          minChunks:1,//敲黑板
          priority: -10//优先级更高
        },
        default: {
          test: /[\\/]src[\\/]js[\\/]/
          minChunks: 2,//一般为非第三方公共模块
          priority: -20,
          reuseExistingChunk: true
        }
      },
      runtimeChunk:{
          name:'manifest'
      }
    }
}

 

UglifyJsPlugin:压缩js代码

//webpack.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
  optimization: {
    minimizer: [new UglifyJsPlugin()],
  },
};

优化

 减少项目体积

为了分析是什么导致构建包为什么会变得这么大,可以安装 webpack-bundle-analyzer 插件,通过它可以直观地查看构建包中所有项目的大小。

npm install —save-dev webpack-bundle-analyzer

对应的需要在 webpack.config.js 中做如下配置:

const { BundleAnalyzerPlugin } = require(‘webpack-bundle-analyzer’)

plugins: [
    ...,
    new BundleAnalyzerPlugin({
        analyzerPort: 8081,
    }),
]

配置完成后再次运行构建 npm start,浏览器会自动打开 http://127.0.0.1:8081,在网页上可以看到构建包中每个文件的详细信息。

1、使用externals排除第三方工具库,不打包进项目中。在index.html中,script标签引入这些工具库。

优点:减少项目体积

问题:直接在index.html中引入,同样导致不需要的工具库被加载;如果cdn不稳定,有可能导致项目瘫痪;

2、webpack 可以用使用url-loader 将静态图片转化为 base64 编码的字符串,并内联在对应的脚本中。大幅度地较少了页面的请求数,所以在开发阶段可以无限制地使用。但是如果在实际环境中,会发现一个很大的问题,将图片越大转化为base 64编码的字符串就越长,将会导致整个 Js 脚本的大小飙升。

url-loader可以通过limit属性对图片分情况处理,当图片小于limit(单位:byte)大小时转base64,大于limit时调用file-loader对图片进行处理。

为了减小脚本的大小,我们需要告诉 webpack 什么情况下采用 url-loader 去内联图片,什么情况下采用其他的 loader。所以首先需要对 url-loader 进行配置:

在 options 中设定一个阈值属性 limit: 15000 ,表明当图片小于该阈值 15kb 时,采用base64内联在对应的脚本中。如果超过,可以利用 fallback 属性指定采用file-loader来处理:

rules: [{
  test: /\.(png|jpg)$/,
  use: {
    loader: 'url-loader',
   options: {
      limit: 15000,
      fallback: 'file-loader',
      name: '[name].[ext]',
    },
  },
}]

file-loader 并不会对文件内容进行任何转换,只是复制一份文件内容,并根据配置为他生成一个唯一的文件名(一般配置都会带上hash,否则很可能由于文件重名而冲突)。webpack会根据参数创建对应的文件,放在 public path 目录下。

3、webpack也可以对图片进行压缩操作,通过image-webpack-loader可以对输出的图片进行指定质量的压缩,下面的配置指定了各个格式的图片的压缩质量,并且通过hash编码重新命名输出:

{
    test: /\.(png|jpg|gif|svg)$/,
    use: [
      'file-loader',
      {
        loader: 'image-webpack-loader',
        options: {
          bypassOnDebug: true,
          mozjpeg: {
            progressive: true,
            quality: 65
          },
          optipng: {
            enabled: false,
          },
          pngquant: {
            quality: '65-90',
            speed: 4
          },
          gifsicle: {
            interlaced: false,
          },
          // the webp option will enable WEBP
          webp: {
            enabled: false,
          },
          limit: 1,
          name: '[name].[ext]?[hash]'
        }
      }
    ]
}

 4 externals在webpack里面是一个很重要的概念,通过externals我们可以将一些重要的库通过cdn的形式加载到页面当中去,这样会提升生产环境中页面的渲染效率。上边有用法介绍。

5 UglifyjsWebpackPlugin,当env方式是production的时候,会进行代码压缩。也可以去掉代码中的console.log:

new UglifyJsPlugin({
    uglifyOptions:{
        compress:{
            warning:false,
            drop_debugger:true,
            drop_console:true
        }
    },
    sourceMap:config.build.productionSourceMap,
    parallel:true
})

优化构建速度

在项目庞大时构建耗时可能会变的很长,每次等待构建的耗时加起来也会是个大数目

1、开启相对费时间,开销大的loader进行缓存处理,可以用webpack的cache-loader,可以将结果缓存到磁盘里。但是保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader。

2、使用happypack可以开启多线程转换loader的插件,可以在开发环境下提高编译速度

插件使用,id即为对应的loader表示:

 在rules里的配置,use为对应的happypack/loader?id=xxx即可:

3、使用DllPlugin动态链接库把开销较大的第三方库抽离出来。(分包)

首先build文件夹添加----webpack.dll.config.js:

var path = require("path");
var webpack = require("webpack");

module.exports = {
  // 要打包的模块的数组
  entry: {
    vendor: ['vue/dist/vue.esm.js','vue-router']
  },
  output: {
    path: path.join(__dirname, '../static/js'), // 打包后文件输出的位置
    filename: '[name].dll.js',// vendor.dll.js中暴露出的全局变量名。
    library: '[name]_library' // 与webpack.DllPlugin中的`name: '[name]_library',`保持一致。
  },
  plugins: [
    new webpack.DllPlugin({
      path: path.join(__dirname, '.', '[name]-manifest.json'),
      name: '[name]_library', 
      context: __dirname
    }),
  ]
};

在package.json的scripts里加上(build文件夹下的webpack.dll.config.js):

"dll": "webpack --config build/webpack.dll.config.js",

运行npm run dll 在static/js下生成vendor-manifest.json;

在build/webpack.base.conf.js里加上:

// 添加DllReferencePlugin插件
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('./vendor-manifest.json')
    })
  ],

然后在index.html中引入vendor.dll.js:

<div id="app"></div>
<script src="./static/js/vendor.dll.js"></script>

至此,配置之后的:
可以看到npm run build后的时间大幅度减少,在dist打包体积上也比之前的小。在项目优化中,可以很大程度上加快项目的构建速度和减少项目的打包体积。 

优化渲染速度

 1、懒加载:又叫延时加载,即在需要的时候进行加载,随用即载

在单页应用中,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,以减少首页加载所用时间。

如何与webpack配合实现组件懒加载?

在webpack配置文件中的output路径配置chunkFilename属性,chunkFilename路径将会作为组件懒加载的路径,chunkFilename用来打包require.ensure方法中引入的模块,如果该方法中没有引入任何模块则不会生成任何chunk块文件: 

output: {
        path: resolve(__dirname, 'dist'),
        filename: options.dev ? '[name].js' : '[name].js?[chunkhash]',
        chunkFilename: 'chunk[id].js?[chunkhash]',
        publicPath: options.dev ? '/assets/' : publicPath
 
},

配合webpack支持的异步加载方法

路由中:

export default new Router({ 
    routes: [ 
        {
            mode: 'history',
            path: '/my',
            name: 'my',
            component:  resolve => require(['../page/my/my.vue'], resolve),   // 懒加载
        },
    ]
})

组件中:

components: { 
    historyTab: resolve => {require(['../../component/historyTab/historyTab.vue'], resolve)},   //懒加载
},

异步组件页面渲染的时候会跳动;但是同步书写不会:

require和require.ensure的区别:

// require用法
require(dependencies: String[], [callback: function(...)])

// require.ensure用法
require.ensure(dependencies: String[], callback: function([require]), [chunkName: String])

require: 同AMD规范的require函数,使用时传递一个模块数组和回调函数,模块都被下载下来且都被执行后才执行回调函数

// webpack.config.amd.js
var path = require("path");
module.exports = {
  entry: "./example.amd.js",
  output: {
    path: path.join(__dirname, "amd"),
    filename: "[name].bundle.js",
    chunkFilename: "[id].chunk.js"
  }
};

// example.amd.js
require(["./module1"], function(module1) {
  console.log("aaa");
  var module2 = require("./module2");
  console.log("bbb");
});

// module1.js
console.log("module1");
module.exports = 1;

// module2.js
console.log("module2");
module.exports = 2;

控制台输出:

module1
aaa
module2
bbb

require.ensure:在需要的时候才下载依赖的模块,当参数指定的模块都下载下来了(下载下来的模块还没执行)便执行参数指定的回调函数。require.ensure会创建一个chunk,且可以指定该chunk的名称,如果这个chunk名已经存在了,则将本次依赖的模块合并到已经存在的chunk中,最后这个chunk在webpack构建的时候会单独生成一个文件。requi.ensure的模块只会被下载下来,不会被执行,只有在回调函数使用require(模块名)后,这个模块才会被执行。

// webpack.config.ensure.js
var path = require("path");
module.exports = {
  entry: "./example.ensure.js",
  output: {
    path: path.join(__dirname, "ensure"),
    filename: "[name].bundle.js",
    chunkFilename: "[name].chunk.js"
  }
};

// example.ensure.js
require.ensure(["./module1"], function(require) {
  console.log("aaa");
  var module2 = require("./module2");
  console.log("bbb");
  require("./module1");
}, 'test');

// module1.js
console.log("module1");
module.exports = 1;

// module2.js
console.log("module2");
module.exports = 2;

控制台输出:

aaa
module2
bbb
module1

多次进出同一个异步加载页面是否会造成多次加载组件?

否,首次需要用到组件时浏览器会发送请求加载组件,加载完将会缓存起来,以供之后再次用到该组件时调用。

在多个地方使用同一个异步组件时是否造成多次加载组件?

否,原因同上。

如果在两个异步加载的页面中分别同步与异步加载同一个组件时是否会造成资源重用?  

会。

在异步加载页面中载嵌入异步加载的组件时对页面是否会有渲染延时影响?

会, 异步加载的组件将会比页面中其他元素滞后出现, 页面会有瞬间闪跳影响。 

以下代码中的参数d就是最终打包成的chunkFilename 中的name,最终a,b,c会被打包到一个叫d.hash.js的文件中去:

chunkFilename: utils.assetsPath('js/[name].[chunkhash].js')

----------------------------------------------------------------------

require.ensure(['./c'],function(require){
   let a = require('./a');
   console.log(a);
},'d');
require.ensure(['./c'],function(require){
   let b = require('./b');
   console.log(b);
},'d');

以上代码可以看出,ensure会把没有使用过的require资源进行独立分成成一个js文件。require.ensure的第一个参数是什么意思呢?[],其实就是 当前这个 require.ensure所依赖的其他 异步加载的模块。如果A 和 B都是异步加载的,B中需要A,那么B下载之前,是不是先要下载A?,所以ensure的第一个参数[]是它依赖的异步模块,但是这里需要注意的是,webpack会把参数里面的依赖异步模块和当前的需要分离出去的异步模块给一起打包成同一个js文件,这里可能会出现一个重复打包的问题, 假设A 和 B都是异步的, ensure A 中依赖B,ensure B中 依赖A,那么会生成两个文件,都包含A和B模块。 如果想加载A require.ensure([‘A.js’],function) 即可。

 

 

 

 

 

原文:
https://www.cnblogs.com/joyco773/p/9049760.html
https://www.cnblogs.com/lusongshu/p/8473318.html
posted @ 2020-04-03 16:07  seeBetter  阅读(219)  评论(0编辑  收藏  举报