Skeanmy / Zero2One

学习笔记的仓库
0 stars 1 forks source link

【webpack】webpack总结 #7

Open Skeanmy opened 4 years ago

Skeanmy commented 4 years ago

webpack

webpack-dev-server --open
--open 打开默认浏览器

分别介绍bundle,chunk,module是什么

resolve

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

resolve:{
  alias:{
    components: './src/components/'
  }
}

当你通过 import Button from 'components/button 导入时,实际上被 alias 等价替换成了 import Button from './src/components/button'

babel

devServer

内存中打包,不设置publicPath时,默认将bundle.js输出到根目录,localhost:8000/bundle.js

webpack dev middleware原理

https://segmentfault.com/a/1190000005614604

课程学习

entry和output

loader

loader的执行是从下到上,从右到左的执行

file-loader

url-loader

可以实现file-loader的所有功能

css-loader

import './style.css'

分析多个文件之间css的依赖关系

{
    loader: 'css-loader',
    options: {
        importLoaders: 2
    }
}

解决了这种场景:在一个scss文件中使用了@import './a.scss'时直接通过css-loader从而解析错误的情况,引入了options后所有引入的css也要走postcss-loaderscss-loader

style-loader

插入html头部的style标签

sass-loader

需要安装以下几个包

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

postcss-loader

css3的样式需要写厂商前缀

// postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

plugin

在webpack的生命周期之中自动做一些事情,在webpack打包后在控制台的输出日志可以看到插件的运行情况。

html-webpack-plugin

打包之后运行

会在打包结束后自动生成html文件,并把打包生成的js文件自动引入到html文件中

plugins: [new HtmlWebpackPlugin({
        template: 'src/index.html'
    }), new CleanWebpackPlugin(['dist'])],

根据src目录下的模板去生成dist目录的模板

clean-webpack-plugin

打包之前运行

打包之前先把之前打包生成的文件删除

SourceMap

通过devtool进行配置

webpackDevServer

devServer: {
    // 服务器启动在哪个文件夹下面
    contentBase: './dist',
    open: true,
    port: 8080
},

实现请求转发

proxy: {
    // 这里可以写正则
    '/react/api': {
        target: 'https://www.dell-lee.com',
        // 实现对https地址的转发
        secure: false,
        pathRewrite: {
            'header.json': 'demo.json'
        },
        // bypass: 跳过的内容
        // changeOrigin: 突破特定网站对origin的限制
        changeOrigin: true,
        headers: {
            host: 'www.dell-lee.com',
            // 也可以在这里配置cookie
        }
    }
}

实现单页面应用跳转

devServer: {
    historyApiFallback: true
}
// 等价于,即对任何路径的请求转发到index.html上,根据它上面的业务逻辑去决定显示什么样的内容
devServer: {
    historyApiFallback: {
        rewrites: [{
            from: /\.*\/,
            to: '/index.html'
        }]
    }
}
devServer: {
    contentBase: './dist',
    open: true,
    port: 8080,
    // 开启HMR功能
    hot: true,
    // 即使HMR不生效,浏览器也不自动刷新
    // HMR失效时不刷新页面,即这个配置只对HMR负责
    hotOnly: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],

配置完HMR需要重启服务

// 如果开启了HMR功能
if(module.hot) {
    // 如果number.js内容变化,则执行回调函数
    module.hot.accept('./number', () => {
        document.body.removeChild(document.getElementById('number'));
        number();
    })
}

Tree Shaking

只打包引入的内容,别的没有使用的东西都不需要,通过“摇树”去除掉不需要的内容。

// 开发环境的配置
optimization: {
    usedExports: true
},
// package.json
// 对所有的模块进行treeshaking
"sideEffects": false

// 对特定模块不进行treeshaking
"sideEffects": ["@babel/polyfill", "*.css"]

两种打包模式

使用webpack-merge第三方模块进行webpack配置文件的合并

一般来说会创建一个build目录来对存放两个环境的配置文件,这时候打包出来的dist目录会位于build这个文件夹下面,为了解决这个问题,需要对配置文件中的有关于路径的地方进行更改

plugins: [
    new HtmlWebpackPlugin({
        template: 'src/index.html'
    }), 
    // 第一个参数数组中的路径是相对于根路径的路径
    new CleanWebpackPlugin(['dist'], {
        // 配置根路径
        root: path.resolve(__dirname, '../')
    })
],
output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
}

Code Spliting(代码分割)

公用代码的拆分

每次请求2Mb的资源一般来说要比并行加载两个1Mb要慢

// 同步代码分割
optimization: {
    splitChunks: {
        chunks: 'all'
    }
},

代码分割和webpack无关,在webpack中进行代码分割有两种方式:

import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。

打包分析,Preloading,Prefetch

Ctrl+Shift+p coverage

缓存问题

在线上环境为了解决缓存的问题,需要对每一次打包的文件进行hash命名

Shimming(垫片)

webpack是基于模块打包的,模块之间不耦合。

如a模块引入了jquery,b模块是一个库,其中使用了jquery,在a中引入了b,会出现b中无法找到jq的情况。

模块内的this指向的是模块本身,不是window对象

库的打包

ts的打包

PWA的打包

EsLint的配置

npx eslint --init
eslint src
module.exports = {
  "extends": "airbnb",
  // 通用的eslint解析器
  "parser": "babel-eslint",
  "rules": {
    "react/prefer-stateless-function": 0,
    "react/jsx-filename-extension": 0
  },
  globals: {
    document: false
  }
};

webpack中配置eslint

module: {
    rules: [{ 
        test: /\.js$/, 
        exclude: /node_modules/, 
        use: ['babel-loader', 'eslint-loader']
    }]
},
devServer: {
    // 打开浏览器黑窗口报告错误
    overlay: true,
},

https://webpack.js.org/loaders/eslint-loader/#install

module.exports = {
  entry: '...',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'eslint-loader',
        options: {
          cache: true,
        },
        force: 'pre'
      }, 'babel-loader'
    ],
  },
};

最佳实践

性能优化--提升打包速度

多页面打包(4-11)

实质是增加entry内容,底下配合着添加HtmlWebpackPlugin

const makePlugins = (configs) => {
    const plugins = [
        new CleanWebpackPlugin(['dist'], {
            root: path.resolve(__dirname, '../')
        })
    ];
    Object.keys(configs.entry).forEach(item => {
        plugins.push(
            new HtmlWebpackPlugin({
                template: 'src/index.html',
                filename: `${item}.html`,
                chunks: ['runtime', 'vendors', item]
            })
        )
    });
    const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
    files.forEach(file => {
        if(/.*\.dll.js/.test(file)) {
            plugins.push(new AddAssetHtmlWebpackPlugin({
                filepath: path.resolve(__dirname, '../dll', file)
            }))
        }
        if(/.*\.manifest.json/.test(file)) {
            plugins.push(new webpack.DllReferencePlugin({
                manifest: path.resolve(__dirname, '../dll', file)
            }))
        }
    });
    return plugins;
}

自定义loader

const path = require('path');

module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    // 使用loader时会触发这个配置
    resolveLoader: {
        // 先去node_modules目录下面去找,再去loaders目录去找
        modules: ['node_modules', './loaders']
    },
    module: {
        rules: [{
            test: /\.js/,
            use: [
                {
                    loader: 'replaceLoader',
                },
                {
                    loader: 'replaceLoaderAsync',
                    options: {
                        name: 'lee'
                    }
                },
            ]
        }]
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
}

loader能够实现的功能--解决源代码的包装问题

自定义plugin

class CopyrightWebpackPlugin {
    apply(compiler) {
        compiler.hooks.compile.tap('CopyrightWebpackPlugin', (compilation) => {
            console.log('compiler');
        })
        // compilation本次打包的内容
        compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, cb) => {
            debugger;
            compilation.assets['copyright.txt']= {
                // 文件内容
                source: function() {
                    return 'copyright by dell lee'
                },
                // 文件大小
                size: function() {
                    return 21;
                }
            };
            cb();
        })
    }
}
module.exports = CopyrightWebpackPlugin;

自定义打包工具

const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
// 默认导出内容是esmodule的导出,需要加一个default
const traverse = require('@babel/traverse').default;
const babel = require('@babel/core');

const moduleAnalyser = (filename) => {
    const content = fs.readFileSync(filename, 'utf-8');
    // 把js代码转换成为一个js对象
    const ast = parser.parse(content, {
        // esmodule模式
        sourceType: 'module'
    });
    const dependencies = {};
    traverse(ast, {
        // 有引入代码时走下面的内容
        ImportDeclaration({ node }) {
            const dirname = path.dirname(filename);
            // 相对路径转绝对路径
            const newFile = './' + path.join(dirname, node.source.value);
            dependencies[node.source.value] = newFile;
        }
    });
    // 使用babel进行代码转换
    const { code } = babel.transformFromAst(ast, null, {
        presets: ["@babel/preset-env"]
    });
    return {
        filename,
        dependencies,
        code
    }
}

// 递归分析文件依赖
const makeDependenciesGraph = (entry) => {
    const entryModule = moduleAnalyser(entry);
    const graphArray = [ entryModule ];
    for(let i = 0; i < graphArray.length; i++) {
        const item = graphArray[i];
        const { dependencies } = item;
        if(dependencies) {
            for(let j in dependencies) {
                graphArray.push(
                    moduleAnalyser(dependencies[j])
                );
            }
        }
    }
    const graph = {};
    graphArray.forEach(item => {
        graph[item.filename] = {
            dependencies: item.dependencies,
            code: item.code
        }
    });
    return graph;
}

// 闭包是最基础的
const generateCode = (entry) => {
    const graph = JSON.stringify(makeDependenciesGraph(entry));
    return `
        (function(graph){
            function require(module) { 
                function localRequire(relativePath) {
                    return require(graph[module].dependencies[relativePath]);
                }
                var exports = {};
                (function(require, exports, code){
                    eval(code)
                })(localRequire, exports, graph[module].code);
                return exports;
            };
            require('${entry}')
        })(${graph});
    `;
}

const code = generateCode('./src/index.js');

脚手架配置

CreateReactApp

  1. npm run eject:把隐藏的配置文件释放出来
  2. OneOf这个字段的配置是对特定后缀的文件进行准确匹配loader,仅仅一次,节约资源

VueCli

npm install -g @vue/cli
# OR
yarn global add @vue/cli
# 检查版本
vue --version
vue create my-app