webpack深入学习笔记

1,777 阅读7分钟

一、在webpack中所有文件都是模块

所以你可以

require('./main.js')
require('./main.css')
require('./main.php')

但webpack只原生支持.js,如果你导入css模块,导入php模块需要相应的Loader来进行支持

二、如何支持css模块

1. 先查找是否有开源的css模块的Loader

style-loader, css-loader

2. 安装对应的Loader

npm i -D style-loader css-loader

3.在配置文件中添加module参数,模块参数
4.在modules参数中添加rules参数,模块参数-规则
5.规则中的每一项都是一种模块的处理规则
const path = require('path');

module.exports = {
    // JavaScript 执行入口文件
    entry: './src/main.js',
    output: {
        // 把所有依赖的模块合并输出到一个 bundle.js 文件
        filename: 'bundle.js',
        // 输出文件都放到 dist 目录下
        path: path.resolve(__dirname, './example/dist'),
    },

    // 新增以下内容,支持css
    module: {
        rules: [
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use: ['style-loader', 'css-loader'],
            }
        ]
    }
};

test是需要处理的文件的正则 use是所需要的Loader,会按倒序依次处理,即先通过css-loader,再通过style-loader

三、use的更多用法

     use: ['style-loader', 'css-loader?minimize'],

minimize是css-loader的参数,表明压缩
另一种写法

use: [
  'style-loader', 
  {
    loader:'css-loader',
    options:{
      minimize:true,
    }
  }
]

四、在require时指定Loader

如果你不想在webpack.config.js中添加对应的规则,你可以在require时添加规则

require('style-loader!css-loader?minimize!./main.css');

使用!分隔,最后一参数为导入的模块

五、css-loader,style-loader的工作全流程

  1. require('./main.css') 引入css文件
  2. test: /.css$/, 识别css文件
  3. css文件被css-loader读取,并保存到了js中
  4. 保存在js中的css内容被style-loader读取,插入到style元素中
  5. 页面样式改变

六、Plugin 插件的作用

Loader是转换器,那Plugin就是插件,插件是作用在Loader上的,相当于功能的拓展。

七、使用Plugin

现在我们要实现把css-loader读取的数据,输出到文件中,而不是保存在js中

1. 查找相应的插件Plugin

extract-text-webpack-plugin

2. 安装相应的插件

npm i -D extract-text-webpack-plugin

3. 在配置文件中添加plugin参数,插件参数
4. 创建插件实例
  plugins: [
    new ExtractTextPlugin({
      // 从 .js 文件中提取出来的 .css 文件的名称
      filename: `[name]_[contenthash:8].css`,
    }),
  ]
5. 把插件作用到Loader转换器上
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  // JavaScript 执行入口文件
  entry: './main.js',
  output: {
    // 把所有依赖的模块合并输出到一个 bundle.js 文件
    filename: 'bundle.js',
    // 把输出文件都放到 dist 目录下
    path: path.resolve(__dirname, './dist'),
  },
  module: {
    rules: [
      {
        // 用正则去匹配要用该 loader 转换的 CSS 文件
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          // 转换 .css 文件需要使用的 Loader
          use: ['css-loader'],
        }),
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      // 从 .js 文件中提取出来的 .css 文件的名称
      filename: `[name]_[contenthash:8].css`,
    }),
  ]
};

八、使用 DevServer 实时预览,热加载

1.安装
npm i -D webpack-dev-server
2.启动
webpack-dev-server

3.热加载启动

webpack-dev-server --hot

普通启动相当于重启,而热加载启动,则只更新变化的部分

4.支持 Source Map, 可通过浏览器查看源码,并打断点

webpack-dev-server --hot --devtool source-map

九、DevServer注意点

DevServer把webpack构建出的内容保存在内存中,而不是在dist目录中,所以在index.html中js的路径要把./dist/bundle.js 改成 bundle.js

十、核心概念

核心概念 通过之前几节的学习,相信你已经对 Webpack 有了一个初步的认识。虽然Webpack 功能强大且配置项多,但只要你理解了其中的几个核心概念,就能随心应手地使用它。 Webpack 有以下几个核心概念。

  • Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
  • Plugin:扩展插件,在 Webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情。
  • Output:输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果。

Webpack 启动后会从 Entry 里配置的 Module 开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module, 就会根据配置的 Loader 去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk。最后 Webpack 会把所有 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。

在实际应用中你可能会遇到各种奇怪复杂的场景,不知道从哪开始。 根据以上总结,你会对 Webpack 有一个整体的认识,这能让你在以后使用 Webpack 的过程中快速知道应该通过配置什么去完成你想要的功能,而不是无从下手。

十一、webpack配置汇总

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {

    // 单入口,单chunk
    entry: './src/main.js',
    // 多入口,单chunk
    entry: [
        './src/main.js',
        './src/main2.js',
    ],
    // 多入口,多chunk
    entry: {
        a: './src/main.js',
        b: [
            './src/main2.js',
            './src/main3.js',
        ]
    },
    // 动态入口
    entry:() => {
        return {
        a: './src/main.js',
        b: [
            './src/main2.js',
            './src/main3.js',
        ]
        }
    },
    // 上下文环境
    context: path.resolve (_dirname ,'app')
    
    // 查找文件的规则
    resolve:{
        // 别名
        alias : {
        '@components' : './src/components'
        },
        // 默认从node_modules中找组件,可添加自定义的组件位置,这样就会先从此处找起,可以节省很多../../这样的写法
        modules: ['./src/components','node_modules'],
        // 一个包可能会提代多份代码,应对不同环境,当你在package中定义了入口main, 又定义了es5-main,那么webpack会根据mainFields的先后查找,仅执行最先找到的
        mainFields : ['es5-main','browser', 'main'],
        // 当require('tom')没带文件后缀时,会依次补上下面的后缀,再找。
        extensions : ['js','json'],
        // 第三方描述文件,你可以改成你自己的自定义文件名
        descriptionFiles: [ 'package.json' ],
        // 必须带文件后缀
        enforceExtension: true
    },
    
    // 查找loader的规则
    resolveLoader: {
        modules: ['./src/my-loader','node_modules'],
        extensions: ['.js','.json'],
        mainFields: ['loader','main']
    }
    
    // 针对不同平台转换代码
    // web为默认,node 使用require加载chunk,async-node使用异步加载chunk,electron-main
    target: 'web',
    // devServer可以生成SourceMap 方便调试
    devtool :'source-map',
    
    // 监听默认是关闭的,可以默认开启
    watch: true,
    watchOption: {},
    
    // 比如你环境里已经引入了全局的jQuery,而webpack里又用了jquery,如果不写这个参数,则重复引入了jquery
    // 此时就用下面这个,webpack里的jquery会替换掉全局中的jQuery
    externals: {
        jquery: 'jQuery'
    }

    output: {
        // 把所有依赖的模块合并输出到一个 bundle.js 文件
        filename: 'bundle.js',
        // 一共有4个变量id,name,hash,chunkhash(默认20位,可以加':8'截取8位)
        filename: '[name].js'
        // 输出文件都放到 dist 目录下
        path: path.resolve(__dirname, './dist'),
        // 资源异步加载,从远程获取资源
        publicPath : 'https://cdn.example.com/assets/'
    },

    module: {
        rules: [
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use: [
                    'css-loader',
                    {
                        loader: 'style-loader',
                        // post 最后执行, pre 最先执行
                        enforce :'post'
                    ],
                // 包含目录
                include: path.resolve(__dirname ,'src')
                // 可以数组
                include: [
                    path.resolve(__dirname ,'src'),
                    path.resolve(__dirname ,'src'),
                ].
                // 排除含目录
                exclude: path.resolve(__dirname ,'src/test'),
                // 不转换
                noParse: /jquery|chartjs/,
                // 转化js时,控制选项
                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: [
        new ExtractTextPlugin({
            // 从 .js 文件中提取出来的 .css 文件的名称
            filename: `[name].css`,
        }),
    ]
};

十二、深入浅出webpack给出的汇总规则

const path = require('path');

module.exports = {
  // entry 表示 入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
  // 类型可以是 string | object | array   
  entry: './app/entry', // 只有1个入口,入口只有1个文件
  entry: ['./app/entry1', './app/entry2'], // 只有1个入口,入口有2个文件
  entry: { // 有2个入口
    a: './app/entry-a',
    b: ['./app/entry-b1', './app/entry-b2']
  },

  // 如何输出结果:在 Webpack 经过一系列处理后,如何输出最终想要的代码。
  output: {
    // 输出文件存放的目录,必须是 string 类型的绝对路径。
    path: path.resolve(__dirname, 'dist'),

    // 输出文件的名称
    filename: 'bundle.js', // 完整的名称
    filename: '[name].js', // 当配置了多个 entry 时,通过名称模版为不同的 entry 生成不同的文件名称
    filename: '[chunkhash].js', // 根据文件内容 hash 值生成文件名称,用于浏览器长时间缓存文件

    // 发布到线上的所有资源的 URL 前缀,string 类型
    publicPath: '/assets/', // 放到指定目录下
    publicPath: '', // 放到根目录下
    publicPath: 'https://cdn.example.com/', // 放到 CDN 上去

    // 导出库的名称,string 类型
    // 不填它时,默认输出格式是匿名的立即执行函数
    library: 'MyLibrary',

    // 导出库的类型,枚举类型,默认是 var
    // 可以是 umd | umd2 | commonjs2 | commonjs | amd | this | var | assign | window | global | jsonp ,
    libraryTarget: 'umd', 

    // 是否包含有用的文件路径信息到生成的代码里去,boolean 类型
    pathinfo: true, 

    // 附加 Chunk 的文件名称
    chunkFilename: '[id].js',
    chunkFilename: '[chunkhash].js',

    // JSONP 异步加载资源时的回调函数名称,需要和服务端搭配使用
    jsonpFunction: 'myWebpackJsonp',

    // 生成的 Source Map 文件名称
    sourceMapFilename: '[file].map',

    // 浏览器开发者工具里显示的源码模块名称
    devtoolModuleFilenameTemplate: 'webpack:///[resource-path]',

    // 异步加载跨域的资源时使用的方式
    crossOriginLoading: 'use-credentials',
    crossOriginLoading: 'anonymous',
    crossOriginLoading: false,
  },

  // 配置模块相关
  module: {
    rules: [ // 配置 Loader
      {  
        test: /\.jsx?$/, // 正则匹配命中要使用 Loader 的文件
        include: [ // 只会命中这里面的文件
          path.resolve(__dirname, 'app')
        ],
        exclude: [ // 忽略这里面的文件
          path.resolve(__dirname, 'app/demo-files')
        ],
        use: [ // 使用那些 Loader,有先后次序,从后往前执行
          'style-loader', // 直接使用 Loader 的名称
          {
            loader: 'css-loader',      
            options: { // 给 html-loader 传一些参数
            }
          }
        ]
      },
    ],
    noParse: [ // 不用解析和处理的模块
      /special-library\.js$/  // 用正则匹配
    ],
  },

  // 配置插件
  plugins: [
  ],

  // 配置寻找模块的规则
  resolve: { 
    modules: [ // 寻找模块的根目录,array 类型,默认以 node_modules 为根目录
      'node_modules',
      path.resolve(__dirname, 'app')
    ],
    extensions: ['.js', '.json', '.jsx', '.css'], // 模块的后缀名
    alias: { // 模块别名配置,用于映射模块
       // 把 'module' 映射 'new-module',同样的 'module/path/file' 也会被映射成 'new-module/path/file'
      'module': 'new-module',
      // 使用结尾符号 $ 后,把 'only-module' 映射成 'new-module',
      // 但是不像上面的,'module/path/file' 不会被映射成 'new-module/path/file'
      'only-module$': 'new-module', 
    },
    alias: [ // alias 还支持使用数组来更详细的配置
      {
        name: 'module', // 老的模块
        alias: 'new-module', // 新的模块
        // 是否是只映射模块,如果是 true 只有 'module' 会被映射,如果是 false 'module/inner/path' 也会被映射
        onlyModule: true, 
      }
    ],
    symlinks: true, // 是否跟随文件软链接去搜寻模块的路径
    descriptionFiles: ['package.json'], // 模块的描述文件
    mainFields: ['main'], // 模块的描述文件里的描述入口的文件的字段名称
    enforceExtension: false, // 是否强制导入语句必须要写明文件后缀
  },

  // 输出文件性能检查配置
  performance: { 
    hints: 'warning', // 有性能问题时输出警告
    hints: 'error', // 有性能问题时输出错误
    hints: false, // 关闭性能检查
    maxAssetSize: 200000, // 最大文件大小 (单位 bytes)
    maxEntrypointSize: 400000, // 最大入口文件大小 (单位 bytes)
    assetFilter: function(assetFilename) { // 过滤要检查的文件
      return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
    }
  },

  devtool: 'source-map', // 配置 source-map 类型

  context: __dirname, // Webpack 使用的根目录,string 类型必须是绝对路径

  // 配置输出代码的运行环境
  target: 'web', // 浏览器,默认
  target: 'webworker', // WebWorker
  target: 'node', // Node.js,使用 `require` 语句加载 Chunk 代码
  target: 'async-node', // Node.js,异步加载 Chunk 代码
  target: 'node-webkit', // nw.js
  target: 'electron-main', // electron, 主线程
  target: 'electron-renderer', // electron, 渲染线程

  externals: { // 使用来自 JavaScript 运行环境提供的全局变量
    jquery: 'jQuery'
  },

  stats: { // 控制台输出日志控制
    assets: true,
    colors: true,
    errors: true,
    errorDetails: true,
    hash: true,
  },

  devServer: { // DevServer 相关的配置
        port: 9000
        proxy: { // 代理到后端服务接口
          '/api': 'http://localhost:3000'
        },
        contentBase: path.join(__dirname, 'public'), // 配置 DevServer HTTP 服务器的文件根目录
        compress: true, // 是否开启 gzip 压缩
        historyApiFallback: true, // 是否开发 HTML5 History API 网页
        hot: true, // 是否开启模块热替换功能
        https: false, // 是否开启 HTTPS 模式
        watchContentBase: true, // 监听内容目录下的文件变化,比如html
    },

    profile: true, // 是否捕捉 Webpack 构建的性能信息,用于分析什么原因导致构建性能不佳

    cache: false, // 是否启用缓存提升构建速度

    watch: true, // 是否开始
    watchOptions: { // 监听模式选项
    // 不监听的文件或文件夹,支持正则匹配。默认为空
    ignored: /node_modules/,
    // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
    // 默认为300ms 
    aggregateTimeout: 300,
    // 判断文件是否发生变化是不停的去询问系统指定文件有没有变化,默认每秒问 1000 次
    poll: 1000
  },
}

十三、多种配置规则

开发环境,生产环境执行不同的配置规则

原理

module.exports = function(env, args){
    if(env.environment === 'dev' ){
        plugin = xxx
    }else{
        plugin = aaa
    }
    return {
        entry,
        plugin,
        module,
        ...
    }
}

env 是env命令参数 webpack --env.environment=dev --e env.bao=foo
args 是全部命令参数 webpack --config --env --devtool

还支持返回一个Promise

module.exports = function(env, args){
    if(env.environment === 'dev' ){
        plugin = xxx
    }else{
        plugin = aaa
    }
    return new Promise((resove, reject)=>{
        setTimeout(()=>{
             return {
                entry,
                plugin,
                module,
                ...
            }   
        }, 5000)
    }
   
}

还支持依次执行多个配置

module.exports = [
    {}, // 常规配置
    {}, // 常规配置2
    function, // 函数配置,
    promise, // 异步函数配置
]

适合的场景是:一个要构建出给npm用,一个要构建出开发用,一个要构建出生产用等等

十四、stage的解释